查找
静态查找:在查找过程中不涉及插入和删除操作的查找
动态查找:涉及了插入和删除操作的查找
查找结构:
线性表: 适用于静态查找
树表: 适用于动态查找
散列表两种均使用
线性表的查找
++顺序查找++:适用于顺序存储和链表存储
顺序表的顺序查找算法(注意:跟往常理解的不一样,顺序查找顺序表是从后往前的==主要是不用再设置检测是否到表尾越界问题==)
顺序表的顺序查找算法:
int SeqSearch(int[],int n,int k)
{
r[0]=k;
i=n;
while(r[i]!=k)
i--;
return i;
}
单链表的顺序查找算法:
int SeqSearch(Node<int> *first,int k)
{
p=first->next;count=1;
while(p!=NULL&p->data!=k)
{
p=p-next;
j++
}
if(p->data==k)return j;
else return 0;
}
此算法的平均查找长度是O(n)
++折半查找++
折半查找要求线性表记录必须是按照++关键字有序++,并且采用++顺序存储++结构。
折半查找非递归算法
int BinSearch(int r[],int n,int k)
{
low=1;high=n;
while(low<=high)
{
mid=(low+high)/2;
if(k<r[mid]) high=mid-1;
else if(k>r[mid]) low=mid-1;
else return mid;
}
return 0;
}
折半查找递归算法
int BinSearch2(int r[],int low,int high,int k)
{
if(low>high) return 0;
else{
mid=(low+high)/2;
if(k<r[mid]) return BinSearch(r,low,mid-1,k);
else if(k>r[mid]) return BinSearch(r,mid-1,high,k);
else return mid;
}
}
折半查找的平均时间复杂度为O(log~2~n)
树表的查找
++二叉排序树++: 二叉排序树的左子树上的所有节点都小于其根结点的值;右子树上的所有结点都大于其根结点的值。
==中序遍历二叉排序树,我们就能得到一个关键字有序的序列。
==二叉排序树的查找性能在O(log~2~n)和O(n)之间==
++平衡二叉树++:平衡二叉树是一个空树或其左右子树的深处差不大于1的二叉排序树。
关键技术在于平衡二叉树在添加或删除结点时候的旋转操作。++不过笔试的时候,不会考++
散列表的查找
注意:散列表中要查找的关键字不能重复(也就是说,不适合重复查找,不过上面的两种表的查找方式,都有这种弊端,要是查重复的方法,还是顺序查找最管用。)
散列函数的设计:
- 直接定址法
- 除留余数法
- 数字分析法
- 平方取中发
- 折叠法
讲真,这些方法就是利用散列函数将存储的位置跟数字之间建立映射。
++处理冲突的方法++
- 开放定址法:就是mod之后冲突就一次线性相加
- 二次探测发:就是mod之后冲突就二次线性轮换相加减
- 随机探测法:mod之后相加的是一个随机的数列
- 拉链法:拉链法就是mod之后冲突后建立一个链表,放在同一个位置
排序
==排序的分类==
按照排序的内容是不是在内存中可以将排序分为内部排序和外部排序。
==排序算法的稳定性==
如果排序中,相同关键字的位置在排序过程中保持不变,那么就称排序是稳定的,反之就称其为不稳定的。
总结;
算法种类 | 最好情况 | 平均情况 | 最差情况 | 空间复杂度 |
---|---|---|---|---|
直接插入排序 | O(n) | O(n^2^) | O(n^2^) | O(1) |
冒泡排序 | O(n) | O(n^2^) | O(n^2^) | O(1) |
简单选择排序 | O(n^2^) | O(n^2^) | O(n^2^) | O(1) |
希尔排序 | 不考虑 | O(1) | ||
快速排序 | O(nlog~2~n) | O(nlog~2~n) | O(n^2^) | O(log~2~n) |
堆排序 | O(nlog~2~n) | O(nlog~2~n) | O(nlog~2~n) | O(1) |
二路归并排序 | O(nlog~2~n) | O(nlog~2~n) | O(nlog~2~n) | O(n) |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | O(r) |
算法稳定性的总结
稳定排序算法 | 直接插入排序、冒泡排序、二路归并排序、基数排序 |
---|---|
不稳定排序算法 | 简单选择排序、希尔排序、快速排序、堆排序、 |
内部排序
插入排序
插入排序主要的思想史,将每一个待排序的纪录按照关键字的大小插入到一个已经排好的有序的序列中。
直接插入排序:就是将待排序的关键字插入到一个已经排好的序列中
直接插入排序:
void InsertSort(int r[],int n)
{
for(i=2;i<=n;i++)
{
r[0]=r[i];
for(j=i-1;r0]<r[j];j--)
r[j+1]=r[j];
r[j+1]=r[0];
}
}
希尔排序:希尔排序的基本思想是:先将整个待排序记录序列分割成若干个子序列,在子序列内部分别进行直接插入排序,待整个序列基本有序时,再对全体记录进行一次直接插入排序。
希尔排序算法;
void ShellSort(int r[],int n)
{
for(d=n/2;d>=1;d=d/2)
{
r[0]=r[i];
for(j=i-d;j>0&&r[0]<r[j];j=j-d)
r[j+d]=r[j];
r[j+d]=r[0];
}
}
}
交换排序
交换排序主要通过交换来实现排序序列。主要有冒泡排序和快速排序两种。
冒泡排序: 基本思想就是:两两比较相邻记录的关键码,如果反序则交换,只要没有反序记录为止。
冒泡排序的主要算法:
void BubbleSort(int r[],int n)
{
exchange=n;
while(exchange!=0)
{
bound=exchange;exchange=0;
for(j=1;j<bound;j++)
if(r[j]>r[j+1])
{
r[j]<-->r[j+1];
exchange=j;
}
}
}
快速排序:快速排序是对冒泡排序的一种改进,在快速排序中, 记录的比较和移动是从两端向着中间进行的。减少了对比和移动的次数。
快速排序的主要算法:
int Partition(SqList &L,int low ,int high)//第一次确定点
{
L.r[0]=L.r[low];
pivotkey=L.r[low];
while(low<high)
{
while(low<high&&L.r[high].key>=pivotkey) --high;
L.r[low]=L.r[high];
while(low<high&&L.r[low].key<=pivotkey) ++low;
L.r[high]=L.r[low];
}
L.r[low]=L.r[0];
return low;
}
void QSort(SqList &L,int low,int high)
{
if(low<high)
{
pivotloc=Partition(L,low,high);
QSort(L,low,pivotloc-1);//确定点之前进行快速排序
QSort(L,pivotloc+1,high);//确定点之后进行快速排序
}
}
选择排序
选择排序是一类借助选择来进行的排序,主要思想史,每趟排序在当前排序序列中选择出关键字最小的纪录,添加到已经排序的序列中,选择排序的特点就是纪录的移动比较少。
简单选择排序:简单选择排序的基本思想就是每一趟找出关键字的最小值,然后排入到已经有序的序列中。
简单选择排序的算法:
void SelectSort(SqList &L)
{
for(i=1;i<L.length;i++)
{
j=SelectMinKey(L,i);//找到从i到队尾的最小值
if(i!=j)
L.r[i]<-->L.r[j];
}
}
int SelectMinkey(SqList &L,int i)
{
m=i;//标点位置
n=a[i];//纪录最小值
for(int i=m;m<L.length;m++)
{
if(a[i]<n)
{
n=a[i];m=i;//传递标点和最小值
}
}
return m;
}
堆排序:大根堆和小根堆的排序,大根堆就是根值比下面的都大,一次类推。这样的话,查找的方便性就比较好做,建立合适的大小跟队,然后根据遍历算法就能得到需要的排序序列。
归并排序
归并排序的思路主要是 :将若干个有序序列进行两两归并,直至所有待排序的纪录都在一个有序序列上为止。
二路归并排序,就是每次排序选择两个有序序列进行归并排序,直到所有的纪录都有序为止,可以将二路归并排序看作是一个倒置的赫夫曼树。
基数排序
基数排序是一个借助关键字进行排序的思想,这个技术排序比较的不是纪录上面的数字,而是其关键字。