//不带“哨兵”
void InsertSort(int A[],int n){
int i,j,temp;
for(i=2;i<n;i++){
if(A[i]<A[i-1]){ //发现倒序
temp=A[i];
for(j=i-1;j>=0&&A[j]>temp;--j) //寻找合适的插入位置
A[j+1]=A[j];
A[j+1]=temp;
}
}
//带“哨兵”
void InsertSort(int A[],int n){
int i,j;
for(i=2;i<=n;i++){
if(A[i]<A[i-1]){ //发现倒序
temp=A[i];
for(j=i-1;A[j]>A[0];--j) //寻找合适的插入位置
A[j+1]=A[j];
A[j+1]=A[0];
}
}
具体实例(49,38,65,97,76,13,27,49)
算法分析
- 空间复杂度:,使用了常数个辅助单元,因而空间复杂度为O(1)
- 时间复杂度:最差O(n^2)、最好O(n)
- 稳定性:稳定
- 链表实现:时间复杂度依然是O(n^2)
void InsertSort(int A[] int n){
int i,j,low,high,mid;
for(i=2;i<=n;i++){
A[0]=A[i];
low=1;high=i-1;
while(low<=high){
mid=(low+high)/2;
if(A[mid]>A[0]) high=mid-1; //可用A[0],也可用A[i]
else low=mid+1
}
for(j=i-1;j>=high+1;--j) //注意这里只能用high,不能用low,mid
A[j+1]=A[j];
A[high+1]=A[0];
}
}
- 与直接插入排序相比,比较关键字次数减少了,但是移动元素的次数没有改变,所以时间复杂度仍然是O(n^2);
- 只能用于顺序存储的线性表;
void ShellSort(int A[],int n){
int i,j;
for(dk=n/2;dk>=1;dk/2)
for(i=dk+1;i<=n;++i){
if(A[i]<A[i-dk]){
A[0]=A[i];
for(j=i-dk;A[j]>A[0]&&j>0;j-=dk)
A[j+dk]=A[j];
A[j+dk]=A[0];
}
}
具体实例
算法分析
- 空间复杂度:仅使用常数个辅助单元,因而空间复杂度为O(1);
- 时间复杂度:最坏情况下O(n^2);
- 稳定性:因为相同元素可能放在不同子序列中,不同子序列内的元素交换,
- 无法确定相同元素不调换排序位置,所以希尔排序是一种不稳定的排序。
void BubbleSort(int A[],int n){
for(int i=0;i<n;i++){
bool flag=false;
for(int j=n-1;j>i;j--) //j>I表明只在未排序序列中交换
if(A[j-1]>A[j]){
swap(A[j-1],A[j]);
flag=true;
}
if(!flag) return; //如果一趟下来都没有发生交换,那么表明整个序列已经有序
}
}
具体实例
算法分析
- 空间复杂度:仅用了常数个辅助变量,因而空间复杂度为O(1);
- 时间复杂度:最好为O(n),最差为O(n^2);
- 适用范围:顺序表、链式表
int Partion(int A[],int low,int high){
int pivot=A[low]
while(low<high){
while(low<high&&A[high>=pivot]) --high;
A[low]=A[high];
while(low<high&&A[low]<=pivot) ++low;
A[high]=A[low];
}
A[low]=pivot;
return low;
}
void QuickSort(int A[],int low,int high){
if(low<high){
int position=Partion(A,low,high);
QuickSort(A,low,position-1);
QuickSort(A,position+1,high);
}
}
- 空间复杂度:因为在递归过程中,每个递归都需要一个辅助变量,那么递归的次数就等于辅助变量的次数,因为递归的次数最好为log2 n,所以空间复杂度最好为O(n),最差为O(n)。
- 时间复杂度:最好O(nlog2n),最坏O(n^2)
- 稳定性:不稳定
void SelectSort(int A[],int n){
for(int i=0;i<n-1;i++){
int min=i;
for(int j=i+1;j<n;j++)
if(A[j]<A[min]) min=j;
if(min!=i)
swap(A[i],A[min]);
}
}
- 空间复杂度:使用一个交换变量,所以空间复杂度为O(1)
- 时间复杂度:O(n^2)
- 稳定性:不稳定
- 适用范围:顺序表、链表
- 堆:可以理解为顺序存储的完全二叉树
- 大根堆:完全二叉树,根>=左右
- 小根堆:完全二叉树,根<=左右
- 堆排序分为两个过程:堆初始化、输出堆顶后调整新堆
void HeadAdjust(int A[],int k,int len){ //调整指定根节点A[k]
A[0]=A[k];
for(int i=k*2;i<=len;i*=2){
if(i<len&&A[i]<A[i+1]) i++; //不管要不要交换,先选出最大的子结点
if(A[0]>=A[i]) break; //不用交换,而且后面是一定调整好的了
else{
A[k]=A[i]
k=i; //这里与A[K]=A[0]相呼应,其实也可以选择A[i]=A[0],每次都交换
}
}
A[k]=A[0];
}
void BuildMaxHeap(int A[],int len){ //从最后一个子树根节点开始调整,调整全部根节点
for(int i=len/2;i>0;--i)
HeadAdjust(A,i,len);
}
void HeapSort(int A[],int len){
BuildMaxHeap(A,len); //堆初始化
for(i=len;i>1;i--){
Swap(A[i],A[1]); //将最大值放在最后,然后重新在指定位置调整
HeapAdjuest(A,1,i-1) //截断最后一位,并且重新从第一位调整
}
}
具体实例
算法分析
- 空间复杂度:使用了常数个辅助单元,所以为O(1)
- 时间复杂度:建堆O(n)+堆排序O(nlogn)=O(nlogn)
- 稳定性:不稳定
//创建辅助数组B
int *B=(int*)malloc(n*sizeof(int));
//A[low,...mid],A[mid+1,...,high]各自有序,将这两部分归并
void Merge(int A[],int low,int mid,int high){
int i,j,k;
for(k=low;k<=high;k++) B[k]=A[k];
for(i=low,j=mid+1,k=low;i<=mid&&j<=high;k++){
if(B[i]<=B[j]) A[k]=B[i++];
else A[k]=B[j++];
}
//剩下没有规定完的部分赋值到尾部,while只会执行一个
while(i<=mid) A[k++]=B[i++];
while(j<=high) A[k++]=B[j++]
}
void MergeSort(int A[],int low,int high){
if(low<high){ //只有low、high指向了同一位才会停止递归
int mid=(low+high)/2;
MergeSort(A,low,mid); //low、mid、high都是传下来的
MergeSort(A,mid+1,high);
Merge(A,low,mid,high);
}
}
- 空间复杂度:这是本章占用辅助空间最多的排序算法,为O(n)
- 时间复杂度:每趟归并为O(n),排序为O(nlogn)
- 稳定性:稳定
- 使用范围:通常用于链表
- 空间复杂度:辅助存储空间为r个队列,即为O( r);
- 时间复杂度:d趟收集分配,一趟分配需要O(n),一趟收集需要O( r),所以时间复杂度为O(d(n+r))
- 稳定性:稳定
- 外部排序指待排序文件较大,内存一次放不下,需存放在外存的文件的排序
- 外部排序常采用归并排序
- 为减少平衡归并中外存读写次数所采取的方法:增大归并路数和减少归并段个数
- 利用败者树增大归并路数
- 利用置换-选择排序增大归并段长度来减少归并个数
- 由长度不等的归并段,进行多路平衡归并,需要构造最佳归并树