快速排序:变种
看懂快排为何退化,以及各变种如何逐个堵住这些漏洞
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
快速排序:变种
看懂快排为何退化,以及各变种如何逐个堵住这些漏洞
变种地图
从中心出发:所有变种都在解决同一个问题——轴点怎么选,选得好坏决定快排的效率。
轴点与划分
上一页的变种地图里,所有分支最终都回到同一个动作:选定轴点,一趟扫过去把它安放到位。这页就拆开看这个共同骨架——轴点落位后到底发生了什么。
报数者站位对应轴点落位,矮个左高个右两堆再各自重排
不变性:划分过程中的守恒
上一页我们说,划分要把序列劈成「小于轴点」和「不小于轴点」两半。可劈的过程中,轴点自己先被抠出来放一边,中间状态千变万化——凭什么最后一定是对的?靠的是每一步都守住的几条「铁律」。
先腾出一间空房(暂存轴点),再不断把不符的物品换入空房,空房位置移动
单调性:循环变量的推进
上一页我们守住了划分的不变性,但有个隐含问题没回答:为什么划分一定会做完?如果两个游标来回踱步、甚至互相错过不停下,循环就永远不会结束。这一页看它们的推进方向。
一人从门口向里、一人从窗边向外,各走各的,碰面即搜完
基础划分的实现
Lomuto 划分的完整实现:一次遍历把小于等于轴点的元素推到左侧,最后把轴点安放到正确位置。
四个焦点对应不变性的两块拼图:i 维护「≤pivot 区」的右边界(不变性),j 的推进保证单调性,最后一步让轴点两侧天然有序于它。
实例:一次完整划分
拿数组 [3,1,4,5,2] 以 2 为轴点,逐步验证不变式。
坏轴点如何拖垮快排
上一页的划分实例里,元素恰好被分得比较均匀。可如果轴点运气很差呢?每次划分都把数组切成「0 个和 n−1 个」,递归就会一路走到底。
分队时一队全空,等于每次只确定一个人的位置
变种之间的取舍对比
Lomuto 与 Hoare 划分最易混淆:交换次数、指针行为、最坏情况各不同,选错会让实现事倍功半。
- 单指针扫描,边界清晰易写
- 交换次数多,约每元素一次
- 全相等数组退化为 O(n²)
- 双指针相向,写对要小心
- 交换少,总交换约一半
- 等值分布均匀,不易退化
变种划分的实例推演
同一数组换成三数取中轴点,重走一遍划分,看结果差在哪。
时间复杂度分析
上一页看到坏轴点拖垮快排,这一页把账算清楚:最好和最坏情形各写出递归式,再看三路划分与随机化如何把期望拉回 O(n log n)。
每刀切一半对应轴点居中;每刀只切下一小块对应退化
空间与稳定性
两个常被混为一谈的性质:栈空间取决于递归深度,稳定性取决于划分方式,二者成因完全不同。
- 最好 O(log n):每次划分均衡
- 最坏 O(n):退化成链式递归
- 尾递归优化可压到 O(log n)
- 稳定与否与递归深度无关
- 划分时远距离交换破坏相对次序
- 所有原地快排变种都不稳定
自测:不变性与退化
快排退化到 O(n²) 的直接原因是什么?
要点收束
- ✓一切变种都靠同一条划分不变性立足
- ✓循环变量单调推进是不变性成立的引擎
- ✓划分写法千变,等价区间收缩本质不变
- ✓坏轴点退化 O(n²) 是不变性的边界代价
- ✓选变种先问:换来的代价值不值
课后思考
先自己想十分钟,再对照参考答案——答案只是路径,不是终点。
参考答案提示:回看不变式页,等号归入哪一侧只影响边界位置;只要小于/大于两侧与轴点的关系不被破坏,最终递归仍正确,但交换次数可能变化。
参考答案方向:三路划分使相等元素集中,但跨区间交换仍打乱次序。可改为稳定划分(如链表版快排)或给元素附原始下标作决胜键,代价是额外空间与比较开销。
参考答案思路:几乎有序时坏轴点风险最高,随机化或三者取中轴点更对症;三路划分主要省重复元素的比较,对有序输入帮助有限。判断瓶颈在轴点质量还是元素分布。