(a2)快速排序:性能分析

官官方信息技术老师·15 页·深入(追求细节与边界)·0 次浏览·38 天前
排序算法复杂度递归

快排性能分析

看完你能说清快排的最坏、平均复杂度从哪来,以及它为何不稳定

按 空格/→ 演示下一步

问一句
1 / 15 页

全部页面点击任意一页,跳回舞台从这页播放

排序算法复杂度递归

快排性能分析

看完你能说清快排的最坏、平均复杂度从哪来,以及它为何不稳定

1第 1 页 · 快排性能分析

不稳定是什么意思

上一页我们说快排平均 O(n log n),但还有个常被追问的短板:它是不稳定排序。不稳定听起来抽象,其实拿两个相等的元素排一下序,位置对不上了,问题就现形了。

什么是稳定
相等的元素排序后,保持原来的先后顺序
快排为何不稳定
分区时元素远程交换,相等元素可能被换到前面
实例
排 [3a, 3b, 1]:分区后 3b 跑到 3a 前面
排队买票对应 →稳定排序

票价相同的两人,排在前面的人不应被插队到后面

2第 2 页 · 不稳定是什么意思

快排为何不稳定

跟踪两个相等元素在划分中如何被换到远端、次序颠倒。

1
初始数组
[5a, 3, 5b, 1],选 1 为基准,5a 在 5b 前面
2
左指针停
从左找大于 1 的数,5a 被选中,等待交换
3
右指针停
从右找小于 1 的数,直到越过 5b,指向 1 自身
4
触发交换
5a 被换到数组远端,跨越了 5b 的位置
5
次序颠倒
划分后 5b 排在 5a 前面,稳定性被破坏
3第 3 页 · 快排为何不稳定

就地:O(1)辅助空间

前面看了快排的时间和不稳定性,还有个常被忽略的问题:它排序百万个数字时,额外要多占多少内存?答案是几乎不用——它不另开新数组,就在原来的数组里翻来换去。

就地交换
分区操作在原数组内完成,不复制数据到新数组
辅助空间 O(1)
只靠几个下标变量,与数组规模无关
递归栈 O(log n)
真正额外开销来自递归调用,平均深度为对数级
最坏 O(n)
划分极不均衡时递归退化成链,栈深可达 n
在案板上切菜对应 →就地排序

案板上的食材直接调换位置,不另拿一个盘子来装

4第 4 页 · 就地:O(1)辅助空间

不稳定+就地意味着什么

「不稳定」和「就地」是一对交换得来的性质:快排用前者换来后者。什么时候这交换划算?

稳定排序(归并类)
  • 相等元素保持原相对顺序
  • 需 O(n) 辅助空间
  • 按多字段二次排序不出错
快排(不稳定+就地)
  • 相等元素顺序被打乱
  • O(1) 辅助空间,原地交换
  • 排原始数值/日志时无感知差异
需要保持相等键的次序(多关键字排序)选左;只关心结果有序且内存紧张选右。
5第 5 页 · 不稳定+就地意味着什么

性能由什么决定

前面说了快排就地又不稳定,那速度呢?回想划分过程:每一层都要把整个数组的元素扫一遍,代价约 O(n)。所以总耗时 = 每层代价 × 层数——层越少越快,层越多越糟。

每层 O(n)
一趟划分要扫描当前区间全部元素
总代价 = 层数×O(n)
n 不变,层数才是性能的开关
均衡划分:O(log n) 层
每次大致对半切,递归深度只有对数级
失衡划分:O(n) 层
每次只切出 1 个元素,退化到 O(n²)
切香肠对应 →递归层数

从中间对半切,刀数最少;每次只切下一小片,刀数暴增

T(n)=O(n×d),d=log⁡n⇒O(nlog⁡n),d=n⇒O(n2)T(n) = O(n \times d),\quad d=\log n \Rightarrow O(n\log n),\quad d=n \Rightarrow O(n^2)
6第 6 页 · 性能由什么决定

最好情况:均匀划分

每次枢轴居中,递归树深度 log n,层层画出均衡划分树

最好情况:均匀划分
每次枢轴居中,递归树深度 log n,层层画出均衡划分树
7第 7 页 · 最好情况:均匀划分

最坏情况:划分一边倒

对比两棵递归树:均匀划分树矮而宽,一边倒树高n层。沿箭头看每层剩多少元素。

图解渲染中…
d1:n项logn层,每层共n次比较d2:n层每层n-1次比较,约n²/2次
8第 8 页 · 最坏情况:划分一边倒

最坏情形的代价累加

python

用代码逐层累加比较次数,验证一边倒划分时代价为何是 n(n+1)/2。

代码高亮加载中…

逐层累加 n+(n-1)+…+2,正是等差求和的前 n 项,计数结果落在 n(n+1)/2 量级。

9第 9 页 · 最坏情形的代价累加

平均情况怎么定义

前面看到最好 O(nlogn)、最坏 O(n²),差在哪?就在每次划分的枢轴位置。真实的枢轴时好时坏,那长期跑下来到底算谁?这就得给「平均」一个严格的说法:取期望。

随机变量视角
把枢轴位置看作随机变量,对划分质量取数学期望
好坏划分混合
各位置等概率出现,均匀划分占主导,坏的只是少数
期望仍是 O(nlogn)
混合后的期望递归深度为对数级,常数因子稍大
打车等红绿灯对应 →枢轴位置期望

单程可能一路绿灯或全红灯,但长期通勤时间取期望后稳定

E[T(n)]=1n∑k=1n(T(k−1)+T(n−k))+O(n)=O(nlog⁡n)E[T(n)] = \frac{1}{n}\sum_{k=1}^{n}\big(T(k-1)+T(n-k)\big) + O(n) = O(n\log n)
10第 10 页 · 平均情况怎么定义

期望递归深度的直觉

从上往下看:每层抽枢轴像掷硬币,攒到一次「好枢轴」规模就缩水,深不到哪里去。

图解渲染中…
b1:中间一半指排名 1/4 到 3/4 之间,占比恰为 1/2e1:至少 1/4 比 3/4 划分,两边规模都 ≤3/4g1:log 底数为 4/3,常数被吞进 O
11第 11 页 · 期望递归深度的直觉

最坏 vs 平均:0.618 之差

同为快排,n log n 和 n² 只差一个划分平衡——但平均常数的来源常被讲错。

平均情况
  • 划分位置均匀分布,非全对称
  • 期望比较次数约 1.39 n log n
  • 1.39 源自 2(1+ln2)≈1.386
最坏情况
  • 划分一边倒,如已有序取端点
  • 每层只消一个元素,代价累加
  • 总比较达 n(n-1)/2 ≈ n²/2
正确的理解是:决定量级的是划分是否平衡;1.39 只是常数,一边倒才让层级从 log n 退化到 n。
12第 12 页 · 最坏 vs 平均:0.618 之差

三条结论一张表

  • ✓工程定位:不稳定+就地,适合内部排序、可接受乱序的场景
  • ✓性能取决于划分质量:划分越均衡,递归树越浅
  • ✓最好/平均 O(n log n),最坏 O(n²) 可靠随机化选主元规避
  • ✓平均与最好只差常数因子,实践中接近理想
延伸主题:随机化快排证明三路划分应对重复键内省排序如何兜底
13第 13 页 · 三条结论一张表

自测:哪种输入最坏

点击作答

下列哪种输入会让采用固定首元素为基准的快排达到最坏时间复杂度?

14第 14 页 · 自测:哪种输入最坏

课后思考

先自己想一会儿,再看参考答案——答案只是思考路径的起点。

1为什么快排不稳定,却是标准库排序的常见底座?需要稳定性时怎么补救?

参考答案回顾三条结论:平均性能好、就地省空间、常数小。补救思路:给元素附下标当次关键字,或改用归并/插入混合。核心是权衡,不是谁全面胜出。

2既然随机选枢统能防最坏,标准库为什么不都用随机化?什么场景反而不用?

参考答案随机会破坏算法的确定性:测试难复现、有实时性要求的系统忌讳最坏波动。工程上常在随机化、三数取中、introsort 兜底之间按场景取舍。

3平均比最坏只快不到 2.4 倍,那防止最坏情况还值得花力气吗?换个场景还成立吗?

参考答案看场景:单次排序差 2.4 倍可接受,但恶意构造的输入可让线上服务被DoS,且最坏在深层递归中还拖垮缓存。重复调用、输入不可信时,防最坏就是刚需。

15第 15 页 · 课后思考