排序在算法中起到很重要的作用,近来复习了很多排序算法,打算在此做个小总结,顺序依次是:插入排序、希尔排序、冒泡排序、 快速排序、选择排序、堆排序、归并排序。
排序在算法中起到很重要的作用,近来复习了很多排序算法,打算在此做个小总结,顺序依次是:
- 插入排序
- 希尔排序
- 冒泡排序
- 快速排序
- 选择排序
- 堆排序
- 归并排序
插入排序
思想:把当前元素插入到前面的有序序列中。
具体步骤:
1. 暂存这个元素的值
2. 查找要插入的位置,并将插入位置到插入元素原本位置的前一个这一段距离的元素后移一位
3. 将暂存的元素值复制到插入位置
代码
void InsertSort(int A[],int n){
int i,j;
for(i=0;i<n;i++){
int temp = A[i];
for(j=i;j>0&&temp<A[j-1];j--){
A[j]=A[j-1];
}
A[j]=temp;
}
}
这种方法也叫直接插入排序,因为查找元素插入位置的时候,是从前往后逐个查找的,效率较为低下。对于有序表,我们可以采取折半查找的方法找到插入位置,然后再依次后移。
代码
void halfSort(int A[],int n){
int i,j,mid,low,high;
for(int i=0;i<n;i++){
int temp = A[i];
low = 0;
high = i-1;
while(low<=high){
mid = (low + high)/2;
if(temp>A[mid]){
low = mid + 1;
}else{
high = mid - 1;
}
}
for(j=i-1;j>=high+1;j--)
A[j+1]=A[j];
A[high+1]=temp;
}
}
希尔排序
### 思想
- 将数组划分成h个小数组,并对这些小数组进行直接插入排序。
- 缩小增量h,比如h/2,对h/2个小数组进行插入排序
- 直到增量h=1,即对整个数组进行插入排序,但实际上此时只需要对数组进行简单地微调就可以了。
void exch(int a[],int i,int j){
int temp = a[i];
a[i]=a[j];
a[j]=temp;
}
void shellSort(int a[],int n){
int h = 1;
while(h<n/3) h =3*h+1;//选择合适的h
while(h>=1){
//将数组变成h有序
for(int i=h;i<n;i++){
//将a[i]插入到a[i-h],a[i-2*h],a[i-3*h]
for(int j=i;j>=h&&a[j]<a[j-h];j-=h){
exch(a,j,j-h);
}
}
h = h / 3;
}
}
冒泡排序
思想:
将每个元素依次和后面的元素进行比较,若前面元素比后面大,则交换二者。直到每个元素都比较完。
void BubbleSort(int a[],int n){
for(int i=1;i<=n;i++){
for(int j=1;j<n;j++){
if(a[j]>a[j+1]){
exch(a,j,j+1);
}
}
}
}
当我们遇到一种近乎有序的冒泡排序时,上面的算法就进行了很多多余的操作。所以我们对其进行优化,如果没有发生交换,其实就已经说明表有序了,这时就可以退出冒泡了。
void BubbleSort(int a[],int n){
for(int i=0;i<n-1;i++){
//表是否有序的标志,若没发生交换,则说明有序,且没必要继续冒泡
int flag = 0;
for(int j=i+1;j<n;j++){
if(a[i]>a[j]){
flag = 1;
exch(a,i,j);//交换
}
}
if(flag == 0)
return ;
}
}
快速排序
思想:
1.选取一个标定点,把比标定点大的元素放在标定点的右边,小的放在左边。
2.继续对标定点左边的一堆元素进行同样的操作,同理右边的元素也是一样。
3.最后划分的规模越来越小,就排序完成了。
代码
//划分
int partition(int a[],int low,int high){
int pivot = a[low];//选取最左边的元素作为标定点
while(low<high){
while(low<high && pivot<=a[high]) --high;
a[low]=a[high];
while(low<high && pivot>=a[low]) ++low;
a[high]=a[low];
}
a[low]=pivot;
return low;
}
//快速排序
void QuickSort(int a[],int low,int high){
if(low<high){
int pivot = partition(a,low,high);
QuickSort(a,low,pivot-1);
QuickSort(a,pivot+1,high);
}
}
选择排序
思想
第i次遍历表的时候,把第i个最小的元素放在第i个位置上。
代码
void SelectionSort(int a[],int n){
for(int i=0;i<n;i++){
int min_index = i;
for(int j=i+1;j<n;j++){
if(a[j]<a[min_index]) min_index=j;
}
if(min_index!=i) exch(a,i,min_index);
}
}
堆排序
思想:
堆分为最大堆和最小堆,下面我们以最大堆为例。最大堆的根节点是最大的,其父节点总是大于结点自身,其子节点小于自身。由于这种性质,我们可以每次把最大堆的根节点放到数组的末尾,然后向下调整堆,使其仍满足最大堆的性质。
简而言之,就是以下几个步骤。
- 删除根节点并记录值
- 向下调整规模减1的堆
代码
//向下调整
void shiftDown(int a[],int k,int len){
while(2*k<=len){
int j=2*k;
if(a[j]<a[j+1]&&j+1<=len){
j++;
}
if(a[k]>a[j])break;
exch(a,k,j);
k=j;
}
}
//建立堆
void BuildHeap(int a[],int len){
for(int i=len/2;i>0;i--){
shiftDown(a,i,len);
}
}
void HeapSort(int a[],int len){
//建立一个最大堆
BuildHeap(a,20);
for(int i=len;i>0;i--){
exch(a,i,1);
shiftDown(a,1,i-1);
}
}
归并排序
思想:
归并排序的核心思想是把大规模的问题转换成若干个小规模的问题。首先,用二分法把数组拆成n/2个,n/4个,…,4个,2个,最后是由一个元素组成的表,然后将其归并为2个元素的有序表,继续归并成4个元素的有序表,最终归并成n个元素的有序表。
void merge(int a[],int l,int mid,int r){
int b[30];
int i,j,k;
for(int p=l;p<=r;p++)
b[p]=a[p];
for(i=l,j=mid+1,k=l;i<=mid && j<=r;k++){
//将值小的放到数组a中去
if(b[i]<b[j]){
a[k]=b[i++];
}else{
a[k]=b[j++];
}
}
while(i<=mid) a[k++]=b[i++];
while(j<=r)a[k++]=b[j++];
}
void mergeSort(int a[],int l,int r){
int mid = (l+r)/2;
if(l<r){
mergeSort(a,l,mid);
mergeSort(a,mid+1,r);
merge(a,l,mid,r);
}
}
算法总结
排序名 | 空间复杂度 | 最好情况 | 最坏情况 | 平均情况 | 稳定性 |
---|---|---|---|---|---|
直接插入排序 | O(1) | 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(n^1.5) | O(n^1.5) | O(n^1.5) | 不稳定 |
快速排序 | O(logn) | O(nlogn) | O(n^2) | O(nlogn) | 不稳定 |
归并排序 | O(n) | O(nlogn) | O(nlogn) | O(nlogn | 稳定 |
堆排序 | O(1) | O(nlogn) | O(nlogn) | O(nlogn | 不稳定 |
桶排序 | O(n+m) | O(n+m) | O(n+m) | O(n+m) | 稳定 |
基数排序 | O(n+r) | O(d(n+r) | O(d(n+r) | O(d(n+r) | 稳定 |
几个有用的小tips:
- 直接插入排序在数据量小或者近乎有序的表中有很高的性能,因此经常和其他排序综合运用。
- 冒泡排序在有序的情况下很好,但其他情况下不是很理想。
- 选择排序无视序列表的初始状态,就是不管是有序还是无序,对于选择排序没有影响,但选择排序本身性能不是很好
- 希尔排序在增量为3的时候,性能比较好。
- 快速排序在有序或者逆序的情况下显得特别蛋疼,但是他的综合性能在内排序中最优。
- 归并排序属于外排序,因此在很大的数据量的情况下,使用归并排序。但是归并排序空间复杂度较高。
- 任何情况下,基于比较的排序算法的时间复杂度的界限都是O(nlogn)