排序算法
内部排序
-插入排序
-直接插入排序
-折半插入排序
-希尔排序
-交换排序
-冒泡排序
-快速排序
-选择排序
-简单选择排序
-堆排序
-归并排序
-基数排序
间接排序
-表排序
外部排序
-多路归并排序
1.直接插入排序
算法思路:每次将一个待排序的记录按其关键字大小插入到前面已排好序的子序列中,指导全部记录插入完成。
性能分析:
空间复杂度O(1)
时间复杂度O(n^2)
算法稳定
适用于顺序表和链表,使用链表的时候可以重前向后查找插入位置
void InsertSort(int arr[],int n){ int i,j; for(int i=2;i<=n;i++){ // arr[0]不存内容,从1开始存起,arr[0]后面会作为哨兵使用 if(arr[i]<arr[i-1]){ arr[0]=arr[i]; for(int j=i-1;arr[0]<arr[j],j--){ arr[j+1]=arr[j]; } arr[j+1]=arr[0]; } } }
2.折半插入排序
算法思路:和插入排序一样,只是在查找插入位置的时候采用折半查找找到插入位置,找到以后一次性移动元素。注意这里采用的是折半查找,因此这种排序算法只适用于顺序表。
算法时间复杂度:O(n^2)
void InsertSort_Binary_Search(int arr[],int n){ int i,j,low,heigh; for(i=2;i<=n;i++){ arr[0]=arr[i]; low=1; heigh=i-1; while(low<=heigh){ int mid=(low+heigh)/2; if(arr[mid]>arr[0]) heigh=mid-1; else //mid<=arr[0]的时候low=mid+1保证算法的稳定性 low=mid+1; } for(j=i;j>low;j--){ arr[j]=arr[j-1]; } arr[low]=arr[0]; } }
3.希尔排序
分析:插入排序在序列初始基本有序的时候,算法时间复杂度接近O(N),希尔排序正式基于这一点改进而来的
思路:把待排序序列中的元素按照[i,i+d,i+2d...]分为若干个子表对每个子表进行直接插入排序,不断降低d的大小,使表基本有序,然后采用一次插入排序,即d取1.希尔提出的d取d1=n/2,di=d(i-1)/2(向下取整)。
性能分析:
空间复杂度O(1)
当n在某个范围时,时间复杂度O(n^1.3),最坏情况下O(n^2)
由于相同关键字会划分到不同子表,因此不稳定。
仅适用于顺序存储。
void ShellSort(int arr[],int n){ for(int d=n/2;d>=1;d=d/2){ for(int i=d+1;i<=n;i++){ if(arr[i]<arr[i-d]){ arr[0]=arr[i]; //arr[0]不是哨兵,只是用来暂存数据元素 int j; for(j=i-d;arr[j]>arr[0]&&j>0;j-=d){ arr[j+d]=arr[j]; } arr[j+d]=arr[0]; } } } }
4.冒泡排序
算法思路:从第一个元素开始,依次比较相邻的两个元素,如果前面的元素大于后面的元素,则交换他们的次序,一趟下来最大的元素的位置就确定了,依次类推,第二趟的时候就确定了第二大元素的位置,进行n-1躺就能确定所有元素的位置。
性能分析:
空间复杂度O(1)
时间复杂度最好O(n),最坏、平均O(n^2)
算法稳定
void BubbleSort(int arr[],int n){ for(int i=n-1;i>0;i--){ int flag=0; for(int j=0;j<i;j++){ if(arr[j]>arr[j+1]){ int temp=arr[j+1]; arr[j+1]=arr[j]; arr[j]=temp; flag=1; } } if(flag==0) //如果某一趟冒泡没有发生交换,则表示序列已经有序 break; } }
5.快速排序
算法思路:每次选择一个元素pivot作为枢轴,将比pivot大的元素放到他的右边,比pivot小的元素放到他的左边,从而确定了pivot的位置。将pivot左右两个子表进行同样的操作,循环递归,直至表长度为1或0.
性能分析:
空间复杂度和递归调用深度有关,最好、平均O(logn),最坏O(n)
时间复杂度和划分是否对称有关,最坏O(n^2),最好、平均O(nlogn)
算法不稳定
快速排序是所有内部排序中算法性能最好的排序
int Partition(int arr[],int low,int high){ int pivot; pivot=arr[low]; while(low<high){ while(high>low&&arr[high]>=pivot) high--; arr[low]=arr[high]; while(low<high&&arr[low]<=pivot) low++; arr[high]=arr[low]; } arr[low]=pivot; return low; } void QuickSort(int arr[],int low,int high){ if(low<high){ int pivot=Partition(arr,low,high); QuickSort(arr,low,pivot-1); QuickSort(arr,pivot+1,high); } }
6.简单选择排序
算法思路:每次在待排序序列中选取一个最小元素放入已排好序列最后,总共进行n-1趟就能完成排序
性能分析:
空间复杂度O(1)
时间复杂度O(n^2)
算法不稳定
void SelectSort(int arr[],int n){ for(int i=0;i<n-1;i++){ int min=i; for(int j=i+1;j<n;j++){ if(arr[min]>arr[j]){ min=j; } } int temp=arr[i]; arr[i]=arr[min]; arr[min]=temp; } }
7.堆排序
算法思路:利用堆(大顶堆)最大元素在第一个位置的特性每次选取出最大元素来排序,因此也属于选择排序,堆的内容请参考堆。
性能分析:
空间复杂度O(1)
时间复杂度O(nlogn) //每次选取一个最大元素后,剩下的元素需要重新建成堆,这个过程需要一次PercDown过程,复杂度为logn, n次选取元素需要nlogn的时间复杂度
算法不稳定 //考虑L={1,2,2},建立堆的时候会变成{2,1,2},那么选择的第一个最大元素2会被置到排列序列的末尾,最终L{1,2,2}
void PercDown(int arr[],int parent,int len){ int child; int temp=arr[parent]; for(;parent*2<=len;parent=child){ child=parent*2; if(child<len&&arr[child]<arr[child+1]) child++; if(arr[child]>temp){ arr[parent]=arr[child]; } else break; } arr[parent]=temp; } void createHeap(int arr[],int n){ for(int i=n/2;i>0;i--){ PercDown(arr,i,n); } } void HeapSort(int arr[],int n){ createHeap(arr,n); for(int i=n;i>1;i--){ int temp=arr[i]; arr[i]=arr[1]; arr[1]=temp; PercDown(arr,1,i-1); } }
8.归并排序
算法思路:先把整个待排序序列分成n个子序列(待排序序列长度为n,这时候每个子序列只含有一个元素,视为已排好序),接下来让相邻的两个序列作为一组,对每组进行排序,得到n/2组,重复这个组合排序的过程,直到只剩下一个序列,就拍好了序。
性能分析:
空间复杂度O(n)
时间复杂度O(nlogn)
算法稳定
int *B=(int*)malloc(size*sizeof(int)); 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=i;i<=mid&&j<=high;k++){ if(B[i]<=B[j]) A[k]=B[i++]; else A[k]=B[j++]; } 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){ int mid=(low+high)/2; mergeSort(A,low,mid); mergeSort(A,mid+1,high); merge(A,low,mid,high); } }
9.基数排序
算法思路:将关键字分为d组,按照关键字权重依次递增的(比如对三位数进行从大到小排序,那么权重序列就是个、十、百),若当前关键字可能取到r个值,则需要开r个队列,按照元素该关键字位的值将其挂到队列上,完成一趟分配,然后按照关键字从大到小(若要求从大到小排序)的顺序收集这些队列上的元素,完成一个关键字位的排序。重复其他关键字位,最终可以得到从大到小的排序结果。
性能分析:
空间复杂度:O(r)
时间复杂度:O(d(n+r))
算法稳定
常用情况:(同时满足以下条件)
1.数据元素的关键字可以方便的拆分为d组,且d较小
2.每组关键字的取值范围不大,即r较小
3.数据元素个数n较大
10.表排序
算法思路:当元素是一个很大的结构体(如一部电影)的时候,移动元素需要大量的时间,因此我们可以采用指针的方式,用指针指向每个元素的关键字,采用适当的排序算法对这些指针的链接顺序进行排序。表排序是一种思想,而不是具体的某种排序算法。
性能分析:
根据选择的排序算法而定。
排序算法比较:(图出自mooc浙江大学数据结构课程)