本文最后更新于 2026-06-17T20:42:45+08:00
1. 排序概念
通常待处理的数据不是单一的值,选择一个字段的值作为排序中比较的依据,该字段称为关键字
若待排序数字中有关键字值相同的元素,经过某种排序后相对先后位置在排序先后没有变化,称为稳定排序算法。若不能保证相对先后位置不变,称为不稳定排序
内排序:数据可放入声明的一组变量
外部排序:排序过程中要进行内外存之间的数据交换,只能分批从文件中读入至内存变量
==无特殊说明,排序后假定是非递减序列==
2. 内排序 2.1 冒泡排序 以两两比较为基础 第一趟排序:最大元素换到尾部; 第二趟排序:次大元素被换到尾部倒数第二个位置; 以此类推直至前面余下的元素个数为1
基础版:
1 2 3 4 5 6 7 8 9 for (int j=n-1 ; j>0 ; j--){ for (int i=0 ; i<j; i++){ if (a[i]>a[i+1 ]){ tmp=a[i]; a[i]=a[i+1 ]; a[i+1 ]=tmp; } } }
若某一趟排序中一次交换都没有发生,说明已经有序了,可据此设置一个flag
改进版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void bubbleSort (int a[], int n) { int i,j; bool change=true ; int tmp; for (j=n-1 ; j>0 && change; j--){ change=false ; for (i=0 ;i<j;i++){ if (a[i]>a[i+1 ]){ tmp=a[i]; a[i]=a[i+1 ]; a[i+1 ]=tmp; change=true ; } } } }
冒泡排序前者大于后者才交换,相等时保持不动,所以是稳定排序
时间复杂度是$o(n^2)$
2.2 插入排序 插入实现:
1 2 3 4 5 6 7 8 void insert (int a[], const int &x) { int i; for (i=n-1 ; i>=0 ; i--){ if (a[i]<=x) break ; else a[i+1 ]=a[i]; } a[i+1 ]=x; }
每一次将第k个元素插入前面k-1个元素的有序序列中
1 2 3 4 5 6 7 8 9 void insertSort (int a[], int n) { int i; int tmp; for (i=1 ; i<n; i++){ tmp=a[i]; insert (a,i,tmp); } }
稳定排序,时间复杂度为o($n^2$)
2.3 希尔排序 先进行预处理:将原始序列按不同步长分成若干子序列,分别进行插入排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void shellSort (int a[], int n) { int step,i,j; int tmp; for (step=n/2 ; step>0 ; step/=2 ){ for (i=step; i<n; i++){ tmp=a[i]; j=i; while ((j-step>=0 ) && (tmp<=a[j-step])){ a[j]=a[j-step]; j-=step; } a[j]=tmp; } } }
不稳定排序,时间复杂度为o($n^{1.3}$)
2.4 归并排序 假设现在有两个有序数组存放在数组a当中,其中第一个从下标low-mid,第二个mid+1-high
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 void merge (int a[], int low, int mid, int high) { int i,j,k; int *c; c=new int [high-low+1 ]; i=low; j=mid+1 ; k=0 ; while ((i<=mid)&&(j<=high)){ if (a[i]<=a[j]){ c[k]=a[i]; i=i+1 ; } else { c[k]=a[j]; j=j+1 ; } k=k+1 ; } while (i<=mid){ c[k]=a[i]; i=i+1 ; k=k+1 ; } while (j<=high){ c[k]=a[j]; j=j+1 ; k=k+1 ; } for (i=0 ; i<high-low+1 ; i++){ a[i+low]=c[i]; } delete [] c; }
每个元素可以看成是长度为1的有序序列,两两归并,形成长度为2的有序序列,再两两归并,反复操作
1 2 3 4 5 6 7 8 9 10 11 12 void mergeSort (int a[], int n) { mergeSort (a,0 ,n-1 ); }void mergeSort (int a[], int low, int high) { int mid; if (low>=high) return ; mid=(low+high)/2 ; mergeSort (a,low,mid); mergeSort (a,mid+1 ,high); merge (a,low,mid,high); }
稳定排序 时间复杂度为o($nlog_2 n$)
2.5 快速排序 选择一个元素作为“标杆”,小于它的元素移到前面,大于它的元素移到后面,对标杆前后两个字序列分别排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void quickSort (int a[], int n) { quickSort (a,0 ,n-1 ); }void quickSort (int a[], int start, int end) { int i,j,hole; int temp; if (end<=start) return ; temp=a[start]; hole=start; i=start; j=end; while (i<j){ while ((j>i) && (a[j]>=temp)) j--; if (j==i) break ; a[hole]=a[j]; hole=j; while ((i<j) && (a[i]<temp)) i++; if (j==i) break ; a[hole]=a[i]; hole=i; } a[hole]=temp; quickSort (a,start,hole-1 ); quickSort (a,hole+1 ,end); }
搜索一趟的时间复杂度为o(n); 若每一次标杆都是中间位置,时间复杂度为$o(nlog_2n)$ 若最不幸,时间复杂度为$o(n^2)$
所以如果需要改进,可以取中间位置为标杆,或者是在首、尾、中间三个位置中找到中间值,将其作为标杆,但是在排序前,要将标杆元素换到首位置上
这种排序是不稳定排序
2.6 选择排序 从左到右给每个位置选择合适的元素 在下标0-n-1选择最小值,放到位置0; 在下标1-n-1选择最小值,放到位置1; 以此类推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void selectSort (int a[],int n) { int i,j,minIndex; int tmp; for (i=0 ;i<n;i++){ minIndex=i; for (j=i+1 ;j<n;j++){ if (a[j]<a[minIndex]) minIndex=j; } if (minIndex==i) continue ; temp=a[i]; a[i]=a[minIndex]; a[minIndex]=temp; } }
时间复杂度为$o(n^2)$ 不稳定排序
2.7 堆排序 大顶堆:完全二叉树中,任意一个结点的值比左右子节点值都大 小顶堆:完全二叉树中,任意一个结点的值比左右结点值都小 大小顶堆都称为堆
操作:将原数组序列看作是一棵完全二叉树顺序存储,调整为最大堆,摘取大顶,换到待处理元素的最后位置。继续调整新的根,变成大顶堆,得次大元素……
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void heapSort (int a[], int n) { int i,j; int temp; for (i=(n-1 )/2 ;i>=0 ;i--) adjust (a,n,i); for (j=n-1 ;j>=1 ;j--){ temp=a[0 ]; a[0 ]=a[j]; a[j]=temp; adjust (a,j,0 ); } }void adjust (int a[], int n, int i) { int maxChild; int temp; while (true ){ maxChild=2 *i+1 ; if (maxChild>n-1 ) return ; if (maxChild+1 <=n-1 ){ if (a[maxChild+1 ]>=a[maxChild]) maxChild++; } if (a[i]>a[maxChild]) return ; temp=a[i]; a[i]=a[maxChild]; a[maxChild]=temp; i=maxChild; } }
时间复杂度为$o(nlogn)$ 是不稳定排序
2.9 基数排序
多关键字排序:主关键字+次关键字
基数排序:基于多关键字排序思想,解决单一关键字排序问题,即把单一关键字的不同位数视作多关键字进行排序
分为最高位优先法(MSD)和最低位优先法(LSD)。最高位优先法的排序分的子序列个数与数字大小有关,比较复杂,一般选用最低位优先法
若元素最大位数为m,算法的复杂度是o(mn)
2.10 总结 ![[7-内排序综合对比.png]]
3. 外排序 3.1 外排序 外排序:元素个数太多,无法一次性载入内存,需要在内外存间进行数字交换。 方法: 预处理:N个记录分批读入内存,通过内排序算法形成有序片段 归并:把这些有序片段归并为有序文件
3.2 预处理 按照内存容量尽可能多地读入数据,在内存进行排序。
3.3 归并 最简单:二路归并,需要四条磁带。无需序列放在2条磁带,排序后进入另外两条磁带。
k路归并需要k+1个磁带即可
假设有三条磁带和34个已排序片段,初始如何分配两条磁带上的片段数目? 若已排序片段是斐波那契数,分解成它的前两项。
3.4 置换选择 可以让在只能容纳p个记录的内存中生成平均长度为2p的初始已排序片段
![[7-置换选择.png]]
时间消耗为o(m),但是如果内存中元素按最小化堆存储,时间消耗为$o(log_2m)$