408数据结构第8章:排序②——性质对比秒杀、场景选择与外部排序

这一篇不再详细讲过程,重点解决408选择题:

复杂度、稳定性、空间、初始状态影响、适用场景、外部排序。


一、先看408最重要总表

排序算法 最好 平均 最坏 空间 稳定性
直接插入 O(n) O(n²) O(n²) O(1) 稳定
折半插入 O(n²)* O(n²) O(n²) O(1) 稳定
希尔 与增量有关 与增量有关 可到O(n²) O(1) 不稳定
冒泡 O(n) O(n²) O(n²) O(1) 稳定
快速 O(n log n) O(n log n) O(n²) O(log n)平均 不稳定
简单选择 O(n²) O(n²) O(n²) O(1) 不稳定
堆排序 O(n log n) O(n log n) O(n log n) O(1) 不稳定
二路归并 O(n log n) O(n log n) O(n log n) O(n) 稳定
基数 O(d(n+r)) 同左 同左 O(n+r) 稳定

说明:

复制代码
折半插入虽然比较次数可降到O(n log n),
但元素移动仍可能O(n²),所以整体仍是O(n²)。

二、稳定性秒杀表

稳定

复制代码
直接插入
折半插入
冒泡
归并
基数

不稳定

复制代码
希尔
快速
简单选择
堆

可以记成:

复制代码
稳定:插冒归基

不稳定:希快选堆

其中"插"包括:

复制代码
直接插入
折半插入

三、哪些算法最受初始序列影响

直接插入

非常受影响。

复制代码
基本有序 -> 很快 O(n)
逆序 -> O(n²)

冒泡

也受影响。

带提前结束标志时:

复制代码
已经有序 -> O(n)

快速排序

非常受pivot和初始序列影响。

如果固定选首元素:

复制代码
基本有序 / 逆序
-> 可能退化到O(n²)

简单选择

几乎不受初始序列影响。

因为:

复制代码
每趟都必须扫描整个未排序区间找最小

比较次数始终:

复制代码
n(n-1)/2

堆排序、归并排序

时间复杂度较稳定:

复制代码
都是O(n log n)

四、408场景选择秒杀

场景1:序列基本有序

优先:

复制代码
直接插入
冒泡(带提前结束)

最典型:

复制代码
直接插入

场景2:n很小

优先:

复制代码
直接插入

原因:

复制代码
实现简单
常数小

场景3:要求稳定

可考虑:

复制代码
插入
冒泡
归并
基数

场景4:要求最坏仍O(n log n)

优先:

复制代码
堆排序
归并排序

不要选快速排序。


场景5:要求额外空间尽量小

可考虑:

复制代码
堆排序 O(1)

归并:

复制代码
O(n)

空间更大。


场景6:大量数据在外存

优先:

复制代码
归并思想

即:

复制代码
外部排序 -> 多路归并

场景7:链表排序

更适合:

复制代码
归并排序

因为链表不适合随机访问,但归并只需要顺序扫描。


五、比较排序的下界

如果排序算法只通过:

复制代码
比较关键字大小

来确定顺序,那么平均意义下至少需要:

复制代码
Ω(n log n)

级别的比较。

因此:

复制代码
比较排序不可能普遍做到O(n)

但:

复制代码
基数排序
计数排序
桶排序

不完全依赖关键字比较,所以可以突破这个限制。


六、快排、堆排、归并三大重点横向对比

|--------|------------|------------|------------|
| 对比 | 快速排序 | 堆排序 | 归并排序 |
| 平均时间 | O(n log n) | O(n log n) | O(n log n) |
| 最坏时间 | O(n²) | O(n log n) | O(n log n) |
| 空间 | 递归栈 | O(1) | O(n) |
| 稳定 | 否 | 否 | 是 |
| 初始序列影响 | 大 | 小 | 小 |
| 核心操作 | 划分 | 堆调整 | 合并 |
| 典型优势 | 平均很快 | 最坏稳定、空间小 | 稳定、适合外排 |

408里如果三者放一起比较,重点看:

复制代码
稳定性
最坏复杂度
空间

七、交换次数和比较次数常考结论

简单选择排序

比较次数:

复制代码
n(n-1)/2

与初始序列无关。

交换次数:

复制代码
最多n-1次

冒泡排序

逆序情况下:

复制代码
交换次数最多

交换次数等于:

复制代码
逆序对数量

这是一个很重要的结论。


插入排序

元素移动量也和:

复制代码
逆序对数量

高度相关。

序列越接近有序:

复制代码
插入排序越快

八、堆排序的高频计算

1. 建大根堆

升序时:

复制代码
父结点 >= 孩子

从最后一个非叶子结点开始向前调整。

若n个元素,从1开始编号:

复制代码
最后一个非叶子结点 = floor(n/2)

所以建堆时:

复制代码
i = floor(n/2), ..., 1

依次向下调整。


2. 每趟输出

升序排序:

复制代码
堆顶最大

把:

复制代码
堆顶 <-> 当前最后元素

交换。

然后:

复制代码
堆大小减1
重新向下调整根

九、归并排序的高频计算

如果有n个元素,每次二路归并:

复制代码
归并趟数 = ceil(log2 n)

例如:

复制代码
n = 10

则:

复制代码
ceil(log2 10) = 4

需要4趟二路归并。

每一趟处理全部n个元素,因此:

复制代码
O(n log n)

十、外部排序:先理解为什么需要它

如果数据太大:

复制代码
内存一次装不下

就只能:

复制代码
磁盘 <-> 内存

反复读写。

这叫:

复制代码
外部排序

外部排序最重要的成本不是CPU比较,而是:

复制代码
磁盘I/O次数

因此目标:

复制代码
尽量减少归并趟数

十一、外部排序标准流程

复制代码
第一步:生成若干初始归并段

第二步:对这些归并段进行多路归并

第三步:不断归并直到只剩一个有序文件

十二、多路归并趟数公式

设:

复制代码
r = 初始归并段数量
k = k路归并

则归并趟数约为:

复制代码
ceil(log_k r)

例如:

复制代码
16个初始归并段
2路归并

需要:

复制代码
log2 16 = 4趟

如果改成:

复制代码
4路归并

则:

复制代码
log4 16 = 2趟

所以:

复制代码
归并路数越大
归并趟数越少
I/O次数越少

十三、败者树是干什么的

多路归并时,每一轮都要从:

复制代码
k个归并段当前元素

中找最小值。

如果每次普通扫描:

复制代码
比较成本较高

败者树用于:

复制代码
快速从k路中找当前最小元素

本质上:

复制代码
减少CPU比较次数

注意:

复制代码
败者树本身不会直接减少归并趟数

归并趟数主要取决于:

复制代码
初始归并段数量r
归并路数k

十四、如何减少外部排序I/O

两个最重要方向:

方法1:增大归并路数k

复制代码
k越大
log_k r越小
归并趟数越少

方法2:减少初始归并段数量r

也就是让:

复制代码
每个初始归并段更长

常见技术:

复制代码
置换-选择排序

它可以生成比内存容量更长的初始归并段。


十五、排序算法"第一反应"表

看到题目关键词,直接联想:

复制代码
基本有序
-> 直接插入

每趟最大值到末尾
-> 冒泡

pivot归位
-> 快速

每趟选最小值
-> 简单选择

大根堆/小根堆
-> 堆排序

两个有序段合并
-> 归并

个位十位百位
-> 基数

外存、磁盘I/O
-> 多路归并

十六、稳定性怎么判断

判断一个排序是否稳定,只看:

复制代码
两个关键字相同的元素
排序前后相对顺序会不会被颠倒

例如:

复制代码
5a 3 5b

排序后如果:

复制代码
3 5a 5b

稳定。

如果可能:

复制代码
3 5b 5a

则不稳定。


十七、408选择题最常见陷阱

1. 快速排序最快,所以任何时候都最好

错。

复制代码
最坏O(n²)
不稳定
递归需要额外空间

2. 折半插入排序是O(n log n)

错。

复制代码
比较少了
移动仍然O(n²)

3. 堆排序稳定

错。

复制代码
不稳定

4. 归并排序空间O(1)

错。

数组上的二路归并一般需要:

复制代码
O(n)

辅助空间。


5. 简单选择排序在原序列有序时会变快

错。

比较次数基本不变。


6. 希尔排序稳定

错。

复制代码
不稳定

十八、考前1分钟总表

复制代码
直接插入:
稳定,O(n²),基本有序最好O(n)

折半插入:
稳定,比较少,整体仍O(n²)

希尔:
不稳定,原地

冒泡:
稳定,O(n²),最好O(n)

快速:
不稳定,平均O(nlogn),最坏O(n²),pivot归位

简单选择:
不稳定,始终O(n²),比较次数固定

堆:
不稳定,O(nlogn),O(1)空间

归并:
稳定,O(nlogn),O(n)空间

基数:
稳定,非比较排序,O(d(n+r))

十九、强化阶段你最需要练什么

排序这一章不建议平均用力。

优先级:

复制代码
第一档:
快速排序
堆排序
归并排序

第二档:
直接插入
冒泡
简单选择

第三档:
希尔
基数
外部排序

每种算法至少练两类题:

复制代码
1. 给初始序列,问一趟之后结果
2. 给性质,判断稳定性/复杂度/适用场景

二十、回去复习的位置

复制代码
408数据结构
-> 第8章 排序
   -> 8.1 基本概念
   -> 插入排序
   -> 交换排序
   -> 选择排序
   -> 归并排序
   -> 基数排序
   -> 外部排序
   -> 排序算法分析与应用

最后真正要做到的是:

复制代码
看到序列变化 -> 认算法
看到场景 -> 选算法
看到性质 -> 秒判断
相关推荐
码匠许师傅1 小时前
【C++ 面试真题】聊聊 C++ 的拷贝构造与拷贝赋值
java·c++·面试
Shell运维手记2 小时前
Linux 常用基础命令学习笔记
linux·运维·笔记·学习·算法·github
杨航 AI2 小时前
O(n log n):线性对数原理拆解 这个是排序算法的黄金复杂度之一。
算法·排序算法
船厂电气自动化ai大模型2 小时前
AI大模型与数学 第32课 函数凹凸性与二阶导数:拐点求解、凹凸区间计算(10道二阶导数计算题)
数据结构·人工智能·python·深度学习·算法
旖旎夜光2 小时前
LeetCode 3:无重复字符的最长子串(滑动窗口) —— 题解
数据结构·c++·算法·leetcode·滑动窗口
极创信息3 小时前
系统安全隐患全方位排查方案:标准化渗透测试全流程
java·opencv·struts·数据挖掘·eclipse·语音识别·hibernate
汉字萌萌哒4 小时前
2019CCF-CSP入门级C++试题解析
java·开发语言·c++
叠层归一研究院4 小时前
AGI 系统(十一):二阶网络 — 二阶递归 × 多主体 (元符号网络)
开发语言·人工智能·算法·php·agi
喜欢的名字被抢了4 小时前
程序出问题怎么查,以及如何让它不掉线
java·运维·数据库