文章目录
- 一、插入排序
- (一)直接插入排序
- (二)折半插入排序
- (三)希尔排序
- 二、交换排序
- (一)冒泡排序
- (二)快速排序
- 三、选择排序
- (一)简单选择排序
- (二)堆排序
- 四、归并排序
- 五、基数排序
- 六、计数排序
- 七、外部排序
一、插入排序
(一)直接插入排序
算法思想:每次将一个待排序元素按其关键字大小插入到已排好序的序列中,直至所有元素都被排完。
1.排序过程:1)比较:将待排序元素与其左侧的已排序序列从后向前比较,直至找到它的位置;2)移动:将其待放位置的原元素与右侧元素全部右移一位;3)对下一个待排序元素继续执行1)、2)操作。
2.举个例子:
3.算法特点:待排序元素左边的序列是已排好序的序列,右边的序列是未排序序列。
4.时间复杂度:
1)最好情况:最好情况是有序序列,每个元素只需要和其左侧第一个元素比较一次,无需移动,故这种情况下的时间复杂度是O(n)。
2)最坏情况:最坏情况是倒序序列,每个元素要每个元素要和其左侧所有元素比较,故这种情况下的时间复杂度是O(n2)。
3)平均情况:取上述最好与最坏情况的平均值作为平均情况下的时间复杂度,故这种情况下的时间复杂度是O(n2)。
5.空间复杂度:仅使用了常数个辅助单元,因而空间复杂度为O(1)。
6.是否稳定: 因为每次插入元素时总是从后往前先比较再移动,所以不会出现相同元素相对位置发生变化的情况,即直接插入排序是一个稳定的排序算法。
7.适用性:适用于顺序存储和链式存储的线性表,采用链式存储时无须移动元素。
8.C语言代码:
void InsertSort(ElemType A[],int n){
int i,j;
for(i=2;i<=n;i++){ //依次将A[2]-A[n]插入前面已排序的序列
if(A[i]<A[i-1]){ //若A[i]关键码小于其前驱,将A[i]插入有序表
A[0]=A[i]; //复制为哨兵,A[0]不存放元素
for(j=i-1;A[0]<A[j];--j){ //从后向前查找待插入元素
A[j+1]=A[j]; //向后挪位
}
A[j+1]=A[0]; //复制到插入位置
}
}
}
(二)折半插入排序
算法思想:对直接插入排序进行改进,折半查找出元素的待插入位置,然后统一地移动待插入位置之后的所有元素。
1.排序过程:1)比较:将待排序元素与其左侧的已排序序列折半比较,直至找到它的位置;2)移动:将其待放位置的原元素与右侧元素全部右移一位;3)对下一个待排序元素继续执行1)、2)操作。
2.举个例子:
3.算法特点:对直接插入排序的改进,仅减少了比较元素的次数,可以提交时间效率,对于数据量不很大的排序表,折半插入排序往往能表现出很好的性能。
4.时间复杂度:
1)最好情况:最好情况是有序序列,折半插入排序减少了元素的平均比较次数,且折半插入的比较次数与初始状态无关,其移动次数与直接插入排序是一样的,故在最好情况下,每个元素需要比较,时间复杂度为O(
n
l
o
g
2
n
nlog_2{n}
nlog2n),其中n是元素个数,O(
l
o
g
2
n
log_2{n}
log2n)是每个元素需要比较的次数。
2)最坏情况:最坏情况是倒序序列,共n个元素,此时每个元素需要比较O( l o g 2 n log_2{n} log2n)次,但每个元素需要移动O(n)次,所以总的时间复杂度和直接插入排序一样,还是O(n2)。
3)平均情况:取上述最好与最坏情况的平均值作为平均情况下的时间复杂度,故这种情况下的时间复杂度是O(n2)。
5.空间复杂度:仅使用了常数个辅助单元,因而空间复杂度为O(1)。
6.是否稳定:同样值的元素,左侧的一定会出现在右侧的前面,故是稳定的。
7.适用性:涉及到给定数组下标找相应的数组元素的问题,所以只适用于顺序存储的线性表。
8.C语言代码:
void InsertSort(ElemType A[],int n){
int i,j,low,high,mid;
for(i=2;i<=n;i++){ //依次将A[2]-A[n]插入前面已排序的序列
A[0]=A[i]; //暂存至A[0]
low=1;high=i-1; //设置折半查找的范围
while(low<=high){
mid=(low+high)/2; //取中间点
if(A[mid]>A[0]){
high=mid-1; //查找左半子表
}
else loe=mid+1; //查找右半子表
}
for(j=i-1;j>=high+1;--j){
A[j+1]=A[j]; //统一后移元素,空出插入位置
}
A[high+1]=A[0]; //插入操作
}
}
(三)希尔排序
算法思想:从前面的分析可知,直接插入排序算法的时间复杂度为O(n2),但若待排序列为“正序”时,其时间效率可提高至O(n),由此可见它更适用于基本有序的排序表和数据量不大的排序表。希尔排序正是基于这两点分析对直接插入排序进行改进而得来的,先将待排序表分割成若干形如L[i,i+d,i+2d,⋯,i+kd]的“特殊”子表,即把相隔某个“增量”的记录组成一个子表,对各个子表分别进行直接插入排序,当整个表中的元素已呈“基本有序”时,再对全体记录进行一次直接插入排序。
1.排序过程:取增量d,使所有距离为d的元素为一组,进行直接插入排序,直至d取到1。
2.举个例子:
3.算法特点:根据希尔排序的特点,可以发现,在希尔排序的过程中,是按增量段有序的,可以通过这个特点判断一个排序过程是否是希尔排序或者是以多大增量进行插入排序的希尔排序。
4.时间复杂度:因为希尔排序的时间复杂度依赖于增量序列的函数,这涉及数学上尚未解决的难题,所以其时间复杂度分析比较困难。当n在某个特定范围时,希尔排序的时间复杂度约为O(n1.3)。在最坏情况下希尔排序的时间复杂度为O(n2)。
5.空间复杂度:仅使用了常数个辅助单元,因而空间复杂度为O(1)。
6.是否稳定: 由于是按增量进行插入排序,可能导致相同值元素相对位置发生变化,因此是不稳定的。
7.适用性:涉及到给定数组下标找相应的数组元素的问题,所以只适用于顺序存储的线性表。
8.C语言代码:
void ShellSort(ElemType A[],int n){
//A[0]只是暂存单元,不是哨兵,当j<=0时,插入位置已到
for(dk=n/2;dk>=1;dk=dk/2){ //步长变化
for(i=dk+1;i<n;++i){
if(A[i]<A[i-dk]){ //需将A[i]插入有序增量子表
A[0]=A[i]; //暂存在A[0];
for(j=i-dk;j>0&&A[0]<A[j];j-=dk){
A[j+dk]=A[j]; //记录后移,寻找插入的位置
}
A[j+dk]=A[0]; //插入
}
}
}
}
二、交换排序
(一)冒泡排序
算法思想:冒泡排序恰如其名,是将最大(最小)元素一个个浮上水面,最终得到有序序列。
1.排序过程:从后往前(或从前往后)两两比较相邻元素的值,若为逆序(即
A[i-1]>A[i]),则交换它们,直到序列比较完,这称为一次冒泡。重复冒泡,直至成为有序序列。
2.举个例子:
3.算法特点:冒泡排序中所产生的有序子序列一定是全局有序的(不同于直接插入排序),也就是说,有序子序列中的所有元素的关键字一定小于(或大于)无序子序列中所有元素的关键字,这样每趟排序都会将一个元素放置到其最终的位置上,因此会有前面的部分元素或者后面的部分元素是有序的。
4.时间复杂度:
1)最好情况:当初始序列有序时,显然第一趟冒泡后flag依然为false(本趟没有元素交换),从而直接跳出循环,比较次数为n-1,移动次数为0,从而最好情况下的时间复杂度为O(n)。
2)最坏情况:最坏情况是逆序序列,此时每个元素需要比较和交换O(n)次,所有元素排完序后比较和交换O(n2)次,也即时间复杂度为O(n2)。
3)平均情况:取上述最好与最坏情况的平均值作为平均情况下的时间复杂度,故这种情况下的时间复杂度是O(n2)。
5.空间复杂度:仅使用了常数个辅助单元,因而空间复杂度为O(1)。
6.是否稳定: 如果是从后往前排,假如是同值元素,后面的元素无法浮到前面的元素的前面,也就是相对位置不会改变,即算法稳定。
7.适用性:冒泡排序适用于顺序存储和链式存储的线性表。
8.C语言代码:
void BubbleSort(ElemType A[],int n){
for(i=0;i<n-1;i++){
flag=false;//表示本趟冒泡是否发生交换的标志
for(j=n-1;j>i;j--){ //一趟冒泡过程
if(A[j-1]>A[j]){ //若为逆序
swap(A[j-1],A[j]); //交换
flag=true;
}
}
if(flag==false) return; //本趟遍历后没有发生交换,说明表已有序
}
}
(二)快速排序
算法思想:基于分治法,其中分是划分为两部分,治是在这两部分上分别采用快速排序。在待排序表L[1.n]中任取一个元素 pivot 作为枢轴(或称基准,通常取首元素),通过一趟排序将待排序表划分为独立的两部分L[1⋯k-1]和L[k+1⋯n],使得L[1⋯k-1]中的所有元素小于pivot,L[k+1.n]中的所有元素大于或等于pivot,则pivot放在了其最终位置L(k)上,这个过程称为一次划分。然后分别递归地对两个子表重复上述过程,直至每部分内只有一个元素或为空为止,即所有元素放在了其最终位置上。
1.排序过程:
1)设两个指针i和j,初值分别为low和 high,取第一个元素(值为low)为枢轴赋值到变量 pivot;
2)指针j从high往前搜索找到第一个小于枢轴的元素,将其交换到i所指位置;指针i从low往后搜索找到第一个大于枢轴的元素,将其交换到j所指位置;
3)重复2),直至i==j;
4)对pivot两边的元素再从1)开始进行快排。
2.举个例子:
3.算法特点:快速排序是所有内部排序算法中平均性能最优的排序算法,其每趟排序都能确定枢轴元素的最终位置。
4.时间复杂度:快速排序的运行时间与划分是否对称有关,有很多方法可以提高算法的效率:一种方法是尽量选取一个可以将数据中分的枢轴元素,如从序列的头尾及中间选取三个元素,再取这三个元素的中间值作为最终的枢轴元素;或者随机地从当前表中选取枢轴元素,这样做可使得最坏情况在实际排序中几乎不会发生。
1)最好情况:在最理想的状态下,即Partition()能做到最平衡的划分,得到的两个子问题的大小都不可能大于 n/2,在这种情况下,快速排序的运行速度将大大提升,此时,时间复杂度为O( n l o g 2 n nlog_2{n} nlog2n)。
2)最坏情况:快速排序的最坏情况发生在两个区域分别包含n-1个元素和0个元素时,这种最大限度的不对称性若发生在每层递归上,即对应于初始排序表基本有序或基本逆序时,就得到最坏情况下的时间复杂度为O(n2)。
3)平均情况:平均情况下为O(n2)。
5.空间复杂度:由于快速排序是递归的,因此需要借助一个递归工作栈来保存每层递归调用的必要信息,其容量与递归调用的最大层数一致。
1)最好情况:最好情况下为O(
l
o
g
2
n
log_2{n}
log2n);
2)最坏情况:最坏情况下,要进行n-1次递归调用,因此栈的深度为O(n);
3)平均情况:平均情况下为O( l o g 2 n log_2{n} log2n)。
6.是否稳定:在划分算法中,若右端区间有两个关键字相同,且均小于基准值的记录,则在交换到左端区间后,它们的相对位置会发生变化,即快速排序是一种不稳定的排序算法。
7.适用性:涉及到给定数组下标找相应的数组元素的问题,所以只适用于顺序存储的线性表。
8.C语言代码:
void QuickSort(ElemType A[],int low,int high){
if(low<high){ //递归跳出的条件
//Partition()是划分操作,将表A划分为满足上述条件的两个子表
int p=Partition(A,low,high); //划分
QuickSort(A,low,p-1); //依次对两个子表进行递归排序
QuickSort(A,p+1,high);
}
}
int Partition(ElemType A[],int low,int high){ //一趟划分
ElemType 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[hlow]; //将比枢轴元素大的移动至右端
}
A[low]=pivot; //枢轴元素存放至最终位置
}
三、选择排序
(一)简单选择排序
算法思想::每一趟(如第i趟)在后面n-i+1(i=1,2,⋯,n-1)个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1趟做完,待排序元素只剩下1个,就不用再选。
1.排序过程:假设排序表为L[1⋯n],第i趟排序即从L[i⋯n]中选择关键字最小的元素与L(i)交换,每一趟排序可以确定一个元素的最终位置,这样经过n-1趟排序就可使得整个排序表有序。
2.举个例子:
3.算法特点:排序过程中前面部分或者后面部分的元素是有序的。
4.时间复杂度:从上述伪码中不难看出,在简单选择排序过程中,元素移动的操作次数很少,不会超过3(n-1)次,最好的情况是移动0次,此时对应的表已经有序;但元素间比较的次数与序列的初始状态无关,始终是n(n-1)/2次,因此时间复杂度始终是O(n2)。
5.空间复杂度:仅使用常数个辅助单元,所以空间效率为O(1)。
6.是否稳定: 在第i趟找到最小元素后,和第i个元素交换,可能会导致第i个元素与含有相同关键字的元素的相对位置发生改变,简单选择排序是一种不稳定的排序算法。
7.适用性:简单选择排序适用于顺序存储和链式存储的线性表,以及关键字较少的情况。
8.C语言代码:
void SelectSort(ElemType A[],int n){
for(i=0;i<n-1;i++){ //共进行n-1趟
min=i; //记录最小元素位置
for(j=i+1;j<n;j++){ //在A[]中选择最小的元素
if(A[j]<A[min]) min=j; //更新最小元素位置
}
if(min!=i)
swap(A[i],A[min]);
}
}
(二)堆排序
算法思想:堆排序的思路很简单:先初始化堆,然后调整堆。
1.排序过程:
1)初始化堆:将存放在L[1⋯n]中的元素从上至下按层排列成一个堆,然后从下至上依次比较左右结点并将较大元素与其对应的父结点交换((以大顶堆为例)),因为堆本身的特点,所以堆顶元素就是最大值。
2)输出堆顶元素后,通常将堆底元素送入堆顶,此时根结点已不满足大顶堆的性质,堆被破坏,将堆顶元素向下调整使其继续保持大顶堆的性质,再输出堆顶元素。如此重复,直到堆中仅剩一个元素为止。
3)如果后续再插入,先将新结点放在堆的末端,再对这个新结点向上执行调整操作。
2.举个例子:
1)初始化堆:
2)调整堆:
3)插入元素:
3.算法特点:每趟都能确定一个元素在其最终位置。
4.时间复杂度:建堆时间为O(n),之后有n-1次向下调整操作,每次调整的时间复杂度为O(h),所以在最好、最坏和平均情况下,堆排序的时间复杂度为O( n l o g 2 n nlog_2{n} nlog2n)。
5.空间复杂度:仅使用了常数个辅助单元,所以空间复杂度为O(1)。
6.是否稳定: 进行筛选时,有可能把后面相同关键字的元素调整到前面,所以堆排序算法是一种不稳定的排序算法。
7.适用性:堆排序仅适用于顺序存储的线性表。
8.C语言代码:
void HeadAdjust(ElemType A[],int k,int len){
//函数将k为根的子树进行调整
A[0]=A[k]; //A[0]暂存子树的根节点
for(i=2*k;i<=len;i*=2){
if(i<len&&A[i]<A[i+1]){
i++; //取key较大的子结点的下标
}
if(A[0]>=A[i]) break; //筛选结束
else{
A[k]=A[i]; //将A[i]调整到双亲结点上
k=i;
}
}
A[k]=A[0]; //将筛选结点的值放入最终位置
}
void BuildMaxHeap(ElemType A[],int len){
for(int i=len/2;i>0;i--){ //从i=[n/2]--1,反复调整堆
HeadAdjust(A,i,len);
}
}
void HeapSort(ElemType A[],int len){
BuildMaxHeap(A,len); //初始建堆
for(i=len;i>1;i--){ //n-1趟的交换和建堆的过程
Swap(A[i],A[1]); //输出堆顶元素(和堆底元素交换)
HeadAdjust(A,1,i-1); //调整,把剩余的i-1个元素整理成堆
}
}
四、归并排序
算法思想:归并排序与上述基于交换、选择等排序的思想不一样,归并的含义是将两个或两个以上的有序表合并成一个新的有序表。
1.排序过程:假定待排序表含有n个记录,则可将其视为n个有序的子表,每个子表的长度为1,然后两两归并,得到「n/2]个长度为2或1的有序表;继续两两归并⋯⋯如此重复,直到合并成一个长度为n的有序表为止,这种排序算法称为二路归并排序。
2.举个例子:
3.算法特点:逐段有序。
4.时间复杂度:每趟归并的时间复杂度为O(n),共需进行「log?n]趟归并,因此算法的时间复杂度为O( n l o g 2 n nlog_2{n} nlog2n)。
5.空间复杂度:Merge()操作中,辅助空间刚好为n个单元,因此算法的空间复杂度为O(n)。
6.是否稳定: 由于 Merge()操作不会改变相同关键字记录的相对次序,因此二路归并排序算法是一种稳定的排序算法。
7.适用性:归并排序适用于顺序存储和链式存储的线性表。
8.C语言代码:
int *B=(int *)malloc(n*sizeof(int)); //辅助数组B
//A[low...mid]和A[mid+...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]; //将A中所有元素复制到B中
for(i=low,j=mid+1,k=i;i<mid&&j<=high;k++){
if(B[i]<=B[j])
A[k]=B[i++]; //将较小值复制到A中
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); //归并
}
}
五、基数排序
算法思想:基数排序是一种很特别的排序算法,它不基于比较和移动进行排序,而基于关键字各位的大小进行排序。基数排序是一种借助多关键字排序的思想对单逻辑关键字进行排序的方法。
1.排序过程:
2.举个例子:
3.算法特点:逐位有序。
4.时间复杂度:基数排序需要进行d趟“分配”和”收集”操作。一趟分配需要遍历所有关键字,时间复杂度为O(n);一趟收集需要合并r个队列,时间复杂度为O®。因此基数排序的时间复杂度为O(d(n+r)),它与序列的初始状态无关。
5.空间复杂度:一趟排序需要的辅助存储空间为r(r个队列:r个队头指针和r个队尾指针),但以后的排序中会重复使用这些队列,所以基数排序的空间复杂度为O®。
6.是否稳定: 每一趟分配和收集都是从前往后进行的,不会交换相同关键字的相对位置,因此基数排序是一种稳定的排序算法。
7.适用性:基数排序适用于顺序存储和链式存储的线性表。
8.C语言代码:
void RadixSort(int* arr, int n){
//max为数组中最大值
int max = arr[0];
int base = 1;
//找出数组中的最大值
for (int i = 0; i < n; i++){
if (arr[i] > max){
max = arr[i];
}
}
//循环结束max就是数组最大值
//临时存放数组元素的空间
int* tmp = (int*)malloc(sizeof(int)*n);
//循环次数为最大数的位数
while (max / base > 0){
//定义十个桶,桶里面装的不是数据本身,而是每一轮排序对应(十、白、千...)位的个数
//统计每个桶里面装几个数
int bucket[10] = { 0 };
for (int i = 0; i < n; i++){
//arr[i] / base % 10可以取到个位、十位、百位对应的数字
bucket[arr[i] / base % 10]++;
}
//循环结束就已经统计好了本轮每个桶里面应该装几个数
//将桶里面的元素依次累加起来,就可以知道元素存放在临时数组中的位置
for (int i = 1; i < 10; i++){
bucket[i] += bucket[i - 1];
}
//循环结束现在桶中就存放的是每个元素应该存放到临时数组的位置
//开始放数到临时数组tmp
for (int i = n - 1; i >= 0; i--){
tmp[bucket[arr[i] / base % 10] - 1] = arr[i];
bucket[arr[i] / base % 10]--;
}
//不能从前往后放,因为这样会导致十位排好了个位又乱了,百位排好了十位又乱了
//把临时数组里面的数拷贝回去
for (int i = 0; i < n; i++){
arr[i] = tmp[i];
}
base *= 10;
}
free(tmp);
}
六、计数排序
算法思想:计数排序也是一种不基于比较的排序算法。计数排序的思想是:对每个待排序元素x,统计小于x的元素个数,利用该信息就可确定x的最终位置。
1.排序过程:假设输入是一个数组 A[n],序列长度为 n,我们还需要两个数组:B[n]存放输出的排序序列,c[k]存储计数值。用输入数组A中的元素作为数组c的下标(索
引),而该元素出现的次数存储在该元素作为下标的数组c中。
2.举个例子:
3.算法特点:计数排序适用于序列中的元素是整数。
4.时间复杂度:上述代码的第1个和第3个 for循环所花的时间为O(k),第2个和第4个 for 循环所花的时间为O(n),总时间复杂度为O(n+k)。因此,当k=O(n)时,计数排序的时间复杂度为O(n);但当k>O(nlogn)时,其效率反而不如一些基于比较的排序(如快速排序、堆排序等)。
5.空间复杂度:计数排序是一种用空间换时间的做法。输出数组的长度为n;辅助的计数数组的长度为k,空间复杂度为O(n+k)。若不把输出数组视为辅助空间,则空间复杂度为O(k)。
6.是否稳定: 相同元素在输出数组中的相对位置不会改变,因此计数排序是一种稳定的排序算法。
7.适用性:计数排序更适用于顺序存储的线性表。计数排序适用于序列中的元素是整数且元素范围(0~k-1)不能太大,否则会造成辅助空间的浪费。
8.C语言代码:
void CountSort(ElemType A[],ElemType B[],int n,int k){
int i,c[k];
for(i=0;i<k;i++){
c[i]=0; //初始化计数数组
}
for(i=0;i<n;i++){ //遍历输入数组,统计每个元素出现的次数
C[A[i]]++; //c[A[i]]保存的是等于A[i]的元素个数
}
for(i=1;i<k;i++){
c[i]=C[i]+C[i-1]; //C[x]保存的是小于或等于x的元素个数
}
for(i=n-1;i>=0;i--){ //从后往前遍历输入数组
B[C[A[i]-1]]=A[i]; //将元素A[i]放在输出数组 B[]的正确位置上
C[A[i]]=C[A[i]]-1;
}
}
七、外部排序
算法思想:上述排序算法都是内部排序,而在许多应用中,经常需要对大文件进行排序,因为文件中的记录很多,无法将整个文件复制进内存中进行排序。因此,需要将待排序的记录存储在外存上,排序时再把数据一部分一部分地调入内存进行序,在排序过程中需要多次进行内存和外存之间的交换。这种排序算法就称为外部排序。
1.排序过程:外部排序通常采用归并排序算法。它包括两个阶段:①根据内存缓冲区大小,将外存上的文件分成若干长度为C的子文件,依次读入内存并利用内部排序算法对它们进行排序,并将排序后得到的有序子文件重新写回外存,称这些有序子文件为归并段或顺串;②对这些归并段进行逐趟归并,使归并段(有序子文件)逐渐由小到大,直至得到整个有序文件为止。
为减少平衡归并中外存读/写次数以提高排序速度所采取的方法:增大归并路数和减少归并段个数。
①利用败者树增大归并路数;
②利用置换-选择排序增大归并段长度来减少归并段个数;
③由长度不等的归并段进行多路平衡归并,需要构造最佳归并树。
关于提高外部排序速度的三种方式,我将在下一篇文章中说明:【408精华知识】提高外部排序速度的三种方式
3.时间效率:文件通常是按块存储在磁盘上的,操作系统也是按块对磁盘上的信息进行读/写的。因为磁盘读/写的机械动作所需的时间远远超过在内存中进行运算的时间(相比而言可以忽略不计),因此在外部排序过程中的时间代价主要考虑访问磁盘的次数,即 I/O 次数。
小总结
1)我们大致可以发现,与二叉树、折半有关的排序,其时间复杂度都有log,这可以帮助我们记忆各种排序的时间复杂度。
2)在排序过程中,每趟都能确定一个元素在其最终位置的有冒泡排序、简单选择排序、堆排序、快速排序,其中前三者能形成全局有序的子序列,后者能确定枢轴元素的最终位置。
3)内部排序(在内存中进行的排序)包括插入、交换、选择、归并、基数、计数。
写在后面
这个专栏主要是我在学习408真题的过程中总结的一些笔记,因为我学的也很一般,如果有错误和不足之处,还望大家在评论区指出。希望能给大家的学习带来一点帮助,共同进步!!!
参考文献
[1]王道408教材(2025版)