第 8 章 排序
408 数据结构第 8 章 排序复习讲义,涵盖核心概念、重点考点与典型题型。
Last updated on
一、第 8 章到底在研究什么?
第 8 章的考纲包括:
-
排序基本概念
-
插入排序
- 直接插入排序
- 折半插入排序
- 希尔排序
-
交换排序
- 冒泡排序
- 快速排序
-
选择排序
- 简单选择排序
- 堆排序
-
二路归并排序
-
基数排序
-
外部排序
-
各种排序算法的比较和应用
资料将排序分成:
排序
├─ 内部排序
│ ├─ 插入排序
│ │ ├─ 直接插入
│ │ ├─ 折半插入
│ │ └─ 希尔排序
│ ├─ 交换排序
│ │ ├─ 冒泡排序
│ │ └─ 快速排序 ★★★★★
│ ├─ 选择排序
│ │ ├─ 简单选择
│ │ └─ 堆排序 ★★★★★
│ ├─ 归并排序 ★★★★★
│ └─ 基数排序
│
└─ 外部排序
└─ 多路归并
├─ 败者树
├─ 置换-选择排序
└─ 最佳归并树内部排序意味着待排序数据能够全部装入内存;外部排序则是数据太大,只能一部分一部分调入内存,因此真正影响性能的往往不是 CPU 比较,而是磁盘 I/O。
二、8.1 排序基本概念
1. 什么叫排序?
有记录:
关键字分别为:
排序就是重新排列这些记录,使得:
最常见的是按关键字非递减排序。
三、一个贯穿全章的核心概念:稳定性
这是选择题超级高频点。
假设有两个关键字相同的元素:
原来:
在
之前。
排序以后如果仍然满足:
则算法稳定。
如果变成:
就不稳定。
一定要记住
稳定:
- 直接插入排序
- 折半插入排序
- 冒泡排序
- 归并排序
- 基数排序
不稳定:
- 希尔排序
- 快速排序
- 简单选择排序
- 堆排序
资料特别指出,在常见平均时间复杂度达到
的比较排序中,归并排序是稳定的。
一个记忆方式:
“插冒归基稳,希快选堆不稳。”
但不要只死记。考研有时会问“为什么不稳定”,你必须会构造反例。
例如简单选择排序:
第一趟把最小的 与 交换:
两个 的相对次序改变,所以不稳定。
四、8.2 插入排序
插入排序统一思想只有一句:
前面已经有序,现在把一个新元素插到正确位置。
就像整理扑克牌。
五、直接插入排序
假设:
有序部分 当前元素 未处理部分
A[1...i-1] A[i] A[i+1...n]处理 时做三件事:
- 找到插入位置;
- 比它大的元素整体后移;
- 把 插进去。
资料使用序列:
例如:
第一开始:
插入 :
插入 :
继续下去,最后得到:
注意两个 的顺序没有变化,所以稳定。
直接插入排序的关键代码思想
资料中的核心代码可以概括为:
for (i = 2; i <= n; i++) {
if (A[i] < A[i - 1]) {
A[0] = A[i];
for (j = i - 1; A[0] < A[j]; j--)
A[j + 1] = A[j];
A[j + 1] = A[0];
}
}这里 是“哨兵/暂存单元”。
需要真正理解:
待插元素先拿出来
↓
比它大的元素不断右移
↓
出现 ≤ 待插元素的元素
↓
插入它后面时间复杂度
最好情况:本身有序
例如:
每个新元素只和前一个比较一下。
所以:
最坏情况:完全逆序
例如:
每次新元素都要一路移动到最前面。
比较、移动量级都是:
因此:
平均:
空间:
稳定。
非常重要的应用判断
如果题目说:
序列已经“基本有序”。
直接插入排序通常非常合适。
这是因为逆序对很少,移动次数很少。
六、折半插入排序
这是一个非常容易出坑的地方。
很多人看到“折半”就认为:
错。
折半插入只是:
用二分查找来寻找插入位置。
原来的直接插入:
寻找位置:顺序查
移动元素:后移折半插入:
寻找位置:二分查找
移动元素:仍然后移虽然寻找插入位置的比较次数降低了,但是移动元素的问题完全没解决。
于是:
但:
最终仍然:
空间:
稳定。
配套模拟题正好考到了:
“折半插入排序的时间复杂度是 。”
这是错误的。资料答案明确指出:比较虽然降为对数量级,但移动仍是平方量级。
七、希尔排序
希尔排序可以理解成:
让元素先“大步移动”,快速消灭远距离逆序;最后再用一次普通插入排序收尾。
直接插入排序最大的问题是:
如果一个很小的数在数组最后:
那么 只能:
一步
一步
一步
一步
...慢慢往前挪。
希尔排序则让它先跨很多格移动。
1. 增量
假设:
可以取:
当:
时:
A1 A5
A2 A6
A3 A7
A4 A8每组分别做插入排序。
然后:
最后:
当 时,本质上就是普通直接插入排序。
但此时整个序列已经非常接近有序,所以最终一趟非常快。
2. 为什么不稳定?
因为两个相等元素可能分进不同的增量组。
一个元素可能经过跨组的大步移动越过另一个相等元素。
所以:
资料指出,希尔排序空间复杂度:
而其时间复杂度依赖增量序列,不能简单像其他排序一样给出统一精确结果;资料给出的常见描述是常见规模下约
最坏可退化到:
并且只适合顺序存储。
考研更重要的不是背 ,而是记住:
希尔排序的时间复杂度与增量序列有关。
八、8.3 交换排序
交换排序:
通过比较元素,并交换它们的位置使序列逐渐有序。
主要两种:
- 冒泡排序
- 快速排序
其中快速排序远重要于冒泡排序。
九、冒泡排序
冒泡排序只比较相邻元素。
例如从左往右:
5 2 4 1比较:
交换:
2 5 4 1再比较:
变成:
2 4 5 1再比较:
变成:
2 4 1 5于是这一趟:
已经到了最终位置。
冒泡的核心特征
每一趟至少确定一个元素的最终位置。
这个特点很重要,因为“根据中间序列判断排序算法”经常考。
资料中的实现带有 flag:
if (本趟完全没交换)
return;如果一整趟一个交换都没有发生,说明:
已经有序,可以提前结束。
复杂度
最好:
最坏:
平均:
空间:
稳定。
为什么稳定?
因为只交换:
如果相等,不交换。
资料还强调:
冒泡每趟形成的已排序部分具有全局有序性,即已经冒出来的元素已经是整个序列的最终元素,而插入排序前面的“有序子序列”只是局部有序。
十、快速排序 ★★★★★
这是本章最重要的算法之一。
一定要理解三件事:
- Partition 划分在干什么;
- 为什么平均 ;
- 为什么有序序列反而可能最坏。
十一、快速排序的核心思想
假设选择:
经过一次划分以后:
所有 < 49 的数 | 49 | 所有 >= 49 的数关键点:
49 已经到最终位置。
但左右两边各自还不一定有序。
于是递归:
QuickSort(左边)
QuickSort(右边)最终完成排序。
资料对序列
执行第一次划分后得到:
这里左侧:
右侧:
所以:
已经永久确定了位置。
十二、Partition 为什么要“右边先找”?
资料的经典版本是:
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;这里有一个很容易困惑的问题:
为什么保存了左端的 pivot 后,要从右边先找?
因为:
A[low]这个位置已经被暂存到 pivot,相当于出现了一个“空位”。
首先从右边找一个:
的数来填左边这个空位。
然后右边又出现空位。
再从左边找一个:
的数去填右边空位。
所以整个过程其实是:
pivot暂存
↓
左边出现坑
↓
从右边找小元素填坑
↓
右边出现坑
↓
从左边找大元素填坑
↓
……
↓
low == high
↓
pivot填最后一个坑这样理解比单纯背代码容易得多。
十三、快速排序为什么快?
假设每次都能非常均匀地分成两半。
第一层:
第二层总元素数仍约:
第三层总元素数仍约:
递归层数约:
所以:
更严格写:
得到:
十四、快速排序最坏情况
假设每次取第一个元素为枢轴。
数组已经递增:
第一次:
得到:
空 | 1 | 2 3 4 5 6第二次:
又是:
空 | 2 | 3 4 5 6每次只减少一个元素。
比较次数接近:
因此:
递归深度:
资料明确指出,初始序列基本有序或基本逆序时,如果枢轴选择不合理,容易出现这种情况。可以采用“三数取中”或随机枢轴改善划分。
十五、快速排序空间复杂度
最好/平均:
原因是递归栈深度约为:
最坏:
因此不能说快速排序是:
空间。
十六、快速排序几个必考结论
快速排序:
- 平均:
- 最坏:
- 平均空间:
- 最坏空间:
- 不稳定
- 适用于顺序存储
- 每次 Partition 至少确定一个枢轴最终位置
资料将其描述为平均性能非常优秀的内部排序方法。
十七、8.4 选择排序
统一思想:
每趟从未排序区中“选”出最小或最大的元素。
主要:
- 简单选择排序
- 堆排序
十八、简单选择排序
例如:
第一趟,从全部元素找最小:
把 和第一个位置交换:
第二趟在:
中找最小。
最重要性质
不管原序列:
已经有序
完全逆序
随机每一趟还是必须把后面的元素全看一遍才能知道谁最小。
所以比较次数始终:
即:
因此无论最好、平均、最坏:
空间:
不稳定。
十九、堆排序 ★★★★★
这一部分一定要和第五章的完全二叉树知识联系起来。
堆本质上是:
用数组存储的完全二叉树,并满足父子大小关系。
二十、大根堆与小根堆
大根堆:
并且:
因此:
每个父亲都不小于孩子。
所以根:
是整个堆的最大值。
小根堆反过来。
二十一、数组与完全二叉树的对应
如果结点下标是:
那么:
左孩子:
右孩子:
双亲:
最后一个非叶结点:
这正是为什么建堆从:
开始。
二十二、建大根堆怎么做?
核心:
从最后一个非叶结点开始,一路向前进行“向下调整”。
代码框架:
for (i = n / 2; i > 0; i--)
HeapAdjust(A, i, n);为什么不能从根开始?
因为向下调整有一个隐含前提:
当前结点的左右子树已经是堆。
所以先处理底部。
二十三、HeapAdjust 向下调整
假设:
20
/ \
50 40这显然不是大根堆。
先在左右孩子中选较大的:
发现:
于是 上移:
50
/ \
20 40然后继续对 所在子树检查。
因此口诀:
大根堆:孩子先选大,父亲比不过就下沉。
小根堆相反:
孩子先选小。
资料中的 HeapAdjust() 正是沿较大的孩子一路向下筛。建堆总时间复杂度不是 ,而是:
这是非常常考的结论。
二十四、堆排序真正过程
若想得到升序:
构造大根堆。
因为最大的元素要放最后。
第一步:
构造大根堆于是:
是最大值。
第二步:
交换:
最大值到最终位置。
第三步:
只对:
重新调整成大根堆。
重复。
所以算法:
建大根堆
↓
堆顶 ↔ 最后一个元素
↓
堆长度 - 1
↓
向下调整
↓
再次交换
……资料中的完整框架就是:
BuildMaxHeap(A, len);
for (i = len; i > 1; i--) {
Swap(A[i], A[1]);
HeapAdjust(A, 1, i - 1);
}二十五、堆排序复杂度
初始建堆:
之后需要输出:
次。
每次重新调整:
所以:
最终:
并且最好、平均、最坏均为:
空间:
不稳定。
这就是堆排序相较快排的一大优势:
不会退化成 。
二十六、Top-K:堆的超级重要应用
资料还专门提到了新考点:
从海量数据中选最大的 个。
比如:
1 亿个数里找最大的 100 个。
不能直接全部排序。
建立大小:
的小根堆。
为什么是小根堆?
因为我们维护的是“目前最大的 100 个数”,其中:
最没资格留下来的,就是这 100 个里面最小的那个。
它正好位于小根堆堆顶。
对新数 :
若:
丢掉。
若:
替换堆顶并调整。
最终复杂度:
空间:
反过来:
找最小 个数 → 建大根堆。
这个经常考反。
二十七、8.5 归并排序 ★★★★★
归并排序核心不是交换,而是:
把两个已有序序列合成一个更大的有序序列。
二十八、二路归并
两个有序序列:
和:
两个指针分别指向头部。
比较:
输出 。
再比较:
输出 。
再:
输出 。
最终:
本质就是:
两个指针
谁小谁走
某一边空了
另一边整体接上二十九、归并排序完整思想
最开始每个元素自身都是一个长度为 的有序段:
[49] [38] [65] [97] [76] [13] [27]第一趟:
[38 49] [65 97] [13 76] [27]第二趟:
[38 49 65 97] [13 27 76]第三趟:
[13 27 38 49 65 76 97]三十、归并排序为什么是 ?
每一层归并,所有元素都处理一次:
从长度 :
需要:
层。
因此:
特别重要:
最好:
平均:
最坏:
它和输入初态基本无关。
三十一、归并排序为什么需要 空间?
因为两个有序段合并时,通常需要辅助数组:
把结果暂存。
因此:
这也是它与堆排序的重要区别。
三十二、归并排序为什么稳定?
假设:
左段:
右段:
比较:
规定:
相等时优先取左段。
于是:
仍在:
前。
所以稳定。
三十三、快排与归并最容易混淆的地方
两者都是分治,但区别非常大。
快排
先:
再递归。
核心工作在“分”。
Partition
→ QuickSort(left)
→ QuickSort(right)归并
先递归分解,再:
核心工作在“合”。
MergeSort(left)
MergeSort(right)
→ Merge所以可以记:
快排:先干活再递归。
归并:先递归再干活。
三十四、8.5.2 基数排序
基数排序很特殊。
前面的快速、堆、归并等本质上都高度依赖:
而基数排序:
不直接进行关键字之间的大小比较。
例如:
可以从个位开始。
LSD:最低位优先
先按个位分桶。
例如十进制:
建立:
Q0
Q1
Q2
...
Q9数字末位是多少,就放哪个队列。
第一轮:
按个位分配 → 收集。
第二轮:
按十位分配 → 收集。
第三轮:
按百位分配 → 收集。
最后整体有序。
资料把过程概括成两步:
分配
若当前位:
则进入:
收集
依次连接:
而且每一位的排序都必须稳定。
基数排序复杂度
设:
- :元素个数
- :关键字位数
- :基数,例如十进制
每一趟:
分配:
收集:
共 趟:
空间:
稳定。
而且时间复杂度基本不取决于原序列是否有序。
三十五、计数排序:非大纲但思想值得知道
资料特别说明:
计数排序本身不在当前统考考纲正式范围内,但其思想在历年算法题中出现过。
本质:
对于元素 ,统计:
假设有 8 个元素小于 ,那么 应放在第:
个位置。
或者对整数值域计数:
C[x] = x出现次数适合:
数据值域较小。
理解即可,不是本章复习的第一优先级。
三十六、8.6 各种内部排序算法比较 ★★★★★
这是整章选择题的核心。
资料的汇总表如下。
| 排序算法 | 最好 | 平均 | 最坏 | 空间 | 稳定 |
|---|---|---|---|---|---|
| 直接插入 | 是 | ||||
| 折半插入 | — | 是 | |||
| 冒泡 | 是 | ||||
| 简单选择 | 否 | ||||
| 希尔 | 与增量有关 | 与增量有关 | 可到 | 否 | |
| 快速 | 平均 | 否 | |||
| 堆 | 否 | ||||
| 二路归并 | 是 | ||||
| 基数 | 同左 | 同左 | 是 |
三十七、几个横向比较一定要会
1. 哪些受初始序列影响很明显?
直接插入:
很明显。
冒泡:
很明显。
快速:
非常明显。
简单选择:
比较次数基本不受影响。
堆:
渐近复杂度不变。
归并:
基本不影响复杂度。
基数:
趟数由关键字位数决定。
三十八、哪些每趟能确定最终位置?
冒泡
每趟至少一个。
简单选择
每趟一个。
堆排序
每次输出一个堆顶,确定一个。
快排
一次划分:
至少枢轴确定最终位置。
资料明确将这类“中间过程特征”列为考点。
三十九、哪些只适合顺序存储?
资料归纳:
- 折半插入
- 希尔
- 快速
- 堆
更适合/要求顺序存储。
而:
- 直接插入
- 冒泡
- 简单选择
- 归并
- 基数
可以用于链式结构。
其中最容易理解的是堆:
因为堆需要快速求:
所以数组非常自然。
四十、排序算法怎么选?★★★★★
资料专门给出了选择规则。
场景 1: 很小
直接插入、简单选择。
如果记录本身非常大,交换/移动记录代价高,则简单选择的移动次数比较少。
场景 2:基本有序
优先:
- 直接插入
- 冒泡
因为最好:
场景 3:数据量大,要求平均速度快
快速排序。
平均:
通常常数较小、实际表现优秀。
场景 4:必须保证最坏也 ,而且空间少
堆排序。
时间:
空间:
缺点:
不稳定。
场景 5:要求稳定,同时要求
归并排序。
这是非常经典的题。
场景 6:关键字位数固定、数量巨大
基数排序。
可以接近线性时间。
四十一、比较排序的理论下界
这是一个重要理论结论。
对于基于比较的排序:
每次比较只有两种结果。
因此整个排序过程可以表示成一棵判定树。
个不同元素共有:
种排列。
所以判定树至少有:
个叶子。
树高至少:
根据 Stirling 公式,其数量级为:
所以:
这就是为什么基数排序、计数排序能做到接近:
因为它们:
不是比较排序。
资料也将该结论列入排序算法选型总结。
四十二、8.7 外部排序 ★★★★
这一部分概念很多,但底层逻辑其实只有一个:
数据太大,内存装不下。
例如一个:
的文件,但机器只有:
内存。
不能直接:
全部读进内存 → QuickSort所以必须:
磁盘
↕
内存不断交换数据。
而磁盘 I/O 比内存比较慢得多,因此资料明确指出:
外部排序性能的主要矛盾是减少磁盘读写次数。
四十三、外部排序基本流程
主要两个阶段:
第一阶段:生成初始归并段
假设内存一次能放 100 万条记录。
读:
前100万条内部排序后写回磁盘:
R1再读下一批:
R2最终得到:
R1 R2 R3 R4 ...它们各自内部有序。
这些叫:
初始归并段 / 顺串。
第二阶段:多路归并
比如有:
进行二路归并:
第一趟:
R1 + R2 → R1'
R3 + R4 → R2'
R5 + R6 → R3'
R7 + R8 → R4'第二趟:
R1' + R2' → R1''
R3' + R4' → R2''第三趟:
R1'' + R2'' → 最终文件四十四、归并趟数公式 ★★★★★
若有:
个初始归并段。
采用:
路归并。
归并趟数:
这公式必须会。
例如:
则:
如果:
则:
所以减少归并趟数有两个方向:
或:
四十五、败者树:解决什么问题?★★★★★
既然增加归并路数:
可以降低归并趟数,那是不是:
越大越好?
普通 路归并中,每次要从 个元素中找最小元素。
朴素方法需要约:
次比较。
如果 很大,内部比较代价也很大。
于是引入:
败者树。
四十六、败者树本质是什么?
可以把它想成:
淘汰赛。
比如 8 个归并段各拿出一个当前最小元素:
R1: 3
R2: 7
R3: 2
R4: 8
...两两比赛。
关键字小的:
胜者,继续向上。
大的:
败者,留在内部结点。
最终根附近得到全局最小者。
输出这个元素后:
只有它所在那一路读取一个新元素。
于是只需要沿着该叶子到根的一条路径重新比赛。
每次选择的比较次数约:
而不是:
四十七、为什么叫“败者树”?
与普通锦标赛树最大的区别:
内部结点记录的是输掉比赛的人。
胜者继续向上。
这样更新一个叶子后,只需沿原路径重新比较,特别适合连续不断执行:
“从 个输入段当前元素中选最小者”。
这正是多路归并的需求。
四十八、归并路数也不是无限大
资料特别提醒:
并不是越大越好。
因为 越大,需要的:
输入缓冲区越多。
如果总内存固定:
于是缓冲区更容易空:
频繁磁盘读取可能导致 I/O 次数重新上升。
所以正确结论:
在内存条件允许范围内适当提高归并路数。
不是:
“归并路数越大一定越好”。
四十九、置换-选择排序 ★★★★
刚才减少归并趟数还有另一条路:
而:
所以办法是:
让每个初始归并段更长。
这就是置换-选择排序。
五十、置换-选择排序怎么工作?
假设内存工作区:
只能装:
个记录。
普通方法:
每个初始归并段长度最多约为 。
置换-选择不同。
步骤:
- 读入 个记录;
- 输出当前最小值 MINIMAX;
- 从磁盘补一个新元素;
- 只能从:
的元素中继续选最小值; 5. 如果工作区再也没有能接在当前归并段后面的元素,则当前归并段结束; 6. 剩余元素进入下一归并段。
一个关键理解
假设刚刚输出:
那当前归并段必须保持升序。
接下来读进:
显然:
所以它:
不能再属于当前归并段。
它被“冻结”起来,留到下一归并段。
而如果读进:
因为:
它还能继续参加当前段。
这就是整个算法最核心的逻辑。
五十一、置换-选择为什么有用?
初始归并段长度可以:
超过内存工作区容量。
对于随机输入,配套模拟资料给出的经典结论是:
平均初始归并段长度约为工作区大小的两倍。
如果工作区:
平均段长度约:
因此归并段数量:
大约减半。
归并趟数:
自然下降。
五十二、最佳归并树 ★★★★
置换-选择以后有个新问题:
初始归并段长度可能不同。
比如:
应该先归并哪些?
如果乱归并:
很长的归并段可能被反复读写很多次。
导致大量 I/O。
因此希望:
小段多归并几次,大段少归并几次。
这跟哈夫曼树完全一样。
所以:
最佳归并树本质就是 叉哈夫曼树。
五十三、为什么 WPL 对应 I/O?
设某初始归并段长度:
它参与:
次归并。
那么这些记录需要被处理:
次。
全部加起来:
它对应整个归并过程中记录读取量。
又因为一次归并通常:
读一次 + 写一次。
所以总 I/O 与:
相关。
资料的例子正是使用 WPL 衡量最佳归并树。
五十四、虚段为什么存在?★★★★★
这是外部排序里非常容易觉得抽象的知识。
如果要构造严格的 叉树,叶子数不能随便取。
严格 叉树中:
因此:
如果:
不是整数,就说明当前初始归并段数量不能恰好组成严格 叉树。
于是补:
权值为 的虚段。
五十五、虚段个数怎么算?
设:
如果:
无需虚段。
如果:
需要补:
个虚段。
也可以理解成:
补到满足
为止。
例如:
则:
所以补:
个虚段。
得到:
个叶结点。
此时:
可以构成严格三叉树。
五十六、本章最容易出的“陷阱”
最后把高频错误集中解决。
错误 1
折半插入排序是 。
错。
因为:
但:
所以总时间仍:
错误 2
快速排序任何情况下都最快。
错。
最坏:
资料配套题也明确把“快速排序时间性能最佳”作为错误表述。
错误 3
有序序列对快速排序最好。
如果固定取首元素为枢轴:
恰恰可能最坏。
错误 4
建堆需要 。
错。
整体建堆:
单次调整才是:
错误 5
堆排序升序应该使用小根堆。
一般经典原地实现中恰好相反:
升序:
因为不断把最大元素放到数组尾部。
错误 6
归并排序空间复杂度 。
错。
递归栈虽约:
但辅助数组:
占主导。
所以:
错误 7
所有 排序都不稳定。
错。
归并排序:
而且稳定。
错误 8
基数排序突破 下界,所以比较排序下界是错的。
错。
限制的是:
基于关键字比较的排序。
基数排序不是比较排序。
错误 9
外部排序主要优化 CPU 比较次数。
错。
最主要的是:
错误 10
路归并的 越大一定越快。
错。
增大 能降低:
但会占用更多输入缓冲区;内存固定时缓冲区变小,可能导致更多 I/O。
五十七、最后给出一张 408 必背总表
| 算法 | 核心动作 | 最好 | 平均 | 最坏 | 空间 | 稳定 | 每趟确定最终位置 |
|---|---|---|---|---|---|---|---|
| 直接插入 | 插入有序区 | ✓ | × | ||||
| 折半插入 | 二分找位置+移动 | — | ✓ | × | |||
| 希尔 | 缩小增量插入 | 与增量有关 | 与增量有关 | 可 | × | × | |
| 冒泡 | 相邻交换 | ✓ | ✓ | ||||
| 快速 | Partition | 平均 | × | 枢轴 | |||
| 简单选择 | 选最小 | × | ✓ | ||||
| 堆排序 | 堆顶交换+调整 | × | ✓ | ||||
| 归并 | 合并有序段 | ✓ | × | ||||
| 基数 | 分配+收集 | 同 | 同 | ✓ | × |
这张表和资料中的排序算法比较部分基本就是第 8 章选择题最核心的知识集合。
五十八、按考研优先级复习
如果按“最值得投入时间”的顺序,建议这一章这样掌握:
第一梯队:必须能手算、能看代码、能分析
-
快速排序
- 一趟 Partition
- 中间结果
- 最好/最坏情况
- 递归深度
- 枢轴最终位置
-
堆排序
- 大/小根堆判断
- 建堆
- 向下调整
- 删除堆顶
- 插入
- Top-K
-
归并排序
- 二路归并
- 递归结构
- 趟数
- 比较次数
- 稳定性
第二梯队:选择题必须熟练
- 直接插入
- 折半插入
- 希尔
- 冒泡
- 简单选择
- 各算法稳定性、复杂度、存储方式、过程特征
第三梯队:外部排序专项
要抓住这一条逻辑链:
于是:
所以:
而:
产生不等长归并段以后:
不能构成严格 叉树:
这样外部排序看似五六个概念,实际上是一条非常完整的因果链。
如果把这一章真正学到“看到任意一个序列,都能手算快速排序一趟、建大根堆和一次堆调整、二路归并一趟、希尔给定增量的一趟、基数分配收集、置换选择初始段”,那么第 8 章绝大多数 408 选择题就已经进入比较稳的状态了。