Appearance
排序中间状态识别
2026 大纲 七(十二)排序算法的分析与应用 · 中间状态反推部分(横向对比与选型见《排序算法对比》)。
一条单向的因果链,倒着走
这类题的形式很固定:给一个序列在排序过程中的某个快照,问"这是哪种排序的第几趟",或者"下列哪个不可能是某种排序的第
它们全都建立在同一条因果链上:
算法怎样扩大有序区
一趟后必然成立的不变量 数组呈现的结构特征
这条链是单向的:算法定了,特征就定了。所以"从中间状态反推算法"就是逆着它往回走——看到什么特征,就去问"哪种扩大有序区的方式会留下这个特征"。
不需要背中间状态清单,只需要记住每种算法"一趟到底干了什么"。
先动手看一眼
八种算法的不变量与特征
| 排序算法 | 不变量(第 | 数组呈现的结构特征 |
|---|---|---|
| 直接/折半插入 | 原序列的前 | 前 |
| 希尔 | 序列是 | 间隔 |
| 冒泡 | 一端的 | 一端聚集 |
| 快速 | 每趟划分使枢轴到达最终位置(左边全 | 存在分界元素;第 1 趟后至少 1 个,前 |
| 简单选择 | 前 | 头部聚集 |
| 堆 | 尾部 | 尾部就位 + 前部是合法大根堆 |
| 二路归并 | 序列被切成若干连续段,每段内部有序,段长 | 连续等长段各自有序 |
| 基数(LSD) | 按已处理的低 | 取每个元素的末 |
这张表里最值得单独拎出来的是四条:
🔴 每趟必有元素就位的只有四个:冒泡、简单选择、快排、堆排。 因为这一趟看过全局(相邻比较有传递性、显式扫一遍未排序区、划分时两端扫描)。插入、希尔、归并、基数都不保证——它们每趟只看过局部(前
个/段内/一位)。
🔴 插入类最硬的指纹是"后缀与初始序列逐位相同",而不是"前缀有序"。前缀有序是好几种算法共有的,后缀没被碰过却只有插入类才有。
🔴 快排就位的不是极值,是枢轴,通常在区间中间。分界元素的个数只能用来排除、不能用来断定趟数。
🔴 直接插入与折半插入的中间状态完全相同、无法区分——两者的不变量本来就一样,只在"找插入位置的方式"上不同。看到这两个同时出现在选项里,说明题目考的一定是别的点。
⚠️ 还有一个措辞坑:希尔的"一趟"指一个增量(把全部
三组最接近的算法,各自怎么区分
① 插入 vs 简单选择——前缀都"有序"。
判别只看一件事:前缀里的元素是不是全局最小的那几个。简单选择 2 趟后
另一条更硬的依据:插入排序的后缀与初始序列逐位相同(还没碰过),而选择排序因为发生过交换,后缀通常已经变样。
机械做法:把前缀里的最大值与后缀里的最小值比一下——前者不大于后者就是"已就位"(选择类),否则只是"局部有序"(插入类)。
② 冒泡 vs 堆排——尾部都是"最大的
判别只能看前部是否满足堆序:按完全二叉树逐个检查
堆排 2 趟后前部
一条更快的粗筛:堆排的前部首元素必是前部的最大值(它就是当前堆顶);冒泡的前部首元素通常不是——冒泡每趟都把大元素往后赶。
③ 归并 vs 希尔——整体都"半有序"。
两者的中间状态都是"半有序",但有序的方向完全不同:归并的有序是连续的(按段长 2、4、8 直接切开,段内有序);希尔的有序是跳跃的(按
归并的连续段边界处通常有一个明显的"跌落"(如
判"不可能是快排第 趟结果"
这是快排在这类题里的固定问法,判断标准是:
🔴 第
趟结束时,至少有 个元素已就位;一个元素就位的标志是"它左边的全部 它,右边的全部 它"。
手法:从左往右扫一遍记录前缀最大值,从右往左扫一遍记录后缀最小值;某个位置若"前缀最大值
以 2, 3, 5, 4, 6, 7, 9 为例(问是否可能是第 2 趟结果):
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| 值 | 2 | 3 | 5 | 4 | 6 | 7 | 9 |
| 前缀最大 | 2 | 3 | 5 | 5 | 6 | 7 | 9 |
| 后缀最小 | 2 | 3 | 4 | 4 | 6 | 7 | 9 |
| 就位? | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ |
5 个就位
⚠️ 两处要说准:① 一个状态里满足分界性质的元素可以多于真正的枢轴数(碰巧分界的元素不矛盾),所以这个计数只能用来排除"分界元素太少"的情形;② 前
反过来用:不变量被违反 = 一定不是这种排序
不变量是必要条件:算法跑完第
| 观察到 | 一定不是 | 理由 |
|---|---|---|
| 后缀与初始序列不一致 | 直接/折半插入的第 | 插入类根本没碰过后缀 |
| 前缀有序但不是全局最小的那几个 | 简单选择排序 | 它的前缀必然是全局最小的 |
| 尾部就位但前部不满足堆序 | 堆排序 | 前部必须始终是合法大根堆 |
| 尾部就位的元素不是全局最大的那几个 | 冒泡、堆排序 | 两者就位的都必须是全局最值 |
| 找不到任何左边全 | 快速排序(任何趟) | 至少有一个枢轴必须已归位 |
| 连续段切不齐、间隔子序列也不有序 | 归并、希尔 | 两者的有序性一个是连续的、一个是跳跃的 |
| 取末 | 基数排序(任何趟) | 第 |
⚠️ 一条不能用的"判断方法":不要拿"元素位置挪动得大不大"去排除基数排序。分配-收集只保证"按低
位有序",并不保证元素位置一定发生变动。反例: 按个位做完第 1 趟分配-收集,序列一个位置都没变(个位依次是 ,本来就有序),但它显然还没排好。只有从不变量导出的条件才能用来排除。
同一序列跑七种排序的对照状态(想逐条核对不变量就展开)
初始序列统一为 (3, 8, 7, 4, 1, 5, 2, 6),下表每个状态都按对应算法逐趟推出:
| 算法(第几趟后) | 状态 | 不变量在这个状态上的体现 |
|---|---|---|
| 直接插入 · 2 趟 | (3, 7, 8, 4, 1, 5, 2, 6) | 前 3 个有序,但 1、2 还在后面——前缀有序 |
| 希尔 | (1, 5, 2, 4, 3, 8, 7, 6) | 间隔 4 看: |
| 冒泡 · 2 趟 | (3, 4, 1, 5, 2, 6, 7, 8) | 尾部 7、8 是全局最大的两个,有序且就位;前部只经历过相邻交换 |
| 快排 · 1 趟(枢轴 3) | (2, 1, 3, 4, 7, 5, 8, 6) | 3 的左边 |
| 简单选择 · 2 趟 | (1, 2, 7, 4, 3, 5, 8, 6) | 头部 1、2 是全局最小的两个,有序且就位 |
| 堆排 · 建初堆 + 2 趟 | (6, 4, 5, 2, 1, 3, 7, 8) | 尾部 7、8 就位;前部 |
| 二路归并 · 2 趟 | (3, 4, 7, 8, 1, 2, 5, 6) | 段长 4 的两个连续段 |
留意冒泡 2 趟与堆排 2 趟的尾部完全相同(都是 7、8 就位)——只看尾部分不出来,必须看前部。
用排除法走一遍:状态
- 后缀被动过
排除插入类; - 头部
不是全局最小的两个(1、2 还在后面) 排除简单选择; - 尾部
是全局最大的两个且有序 剩下冒泡或堆排; - 前部
检查堆序: ,第一步就违反 排除堆排; - 只剩冒泡排序,且是第 2 趟。
"每趟至少一个元素就位"为什么恰好是那四个? 因为"就位"意味着这个元素与其余所有元素的相对位置已经全部确定:交换类与选择类每趟都做了一次"与整个未排序区的比较",所以能得出结论;插入类每趟只看了前
五类不变量的展开说明(想弄清每条特征怎么从算法推出来就展开)
插入类:为什么"前缀有序"却不能说"前缀就位"。 插入排序把第
选择类与交换类:一端聚集全局最值。 简单选择每趟从未排序区选出全局最小接到有序区末尾,冒泡每趟把全局最大交换到未排序区末尾——两者都保证"就位的一定是全局最值"。
快速排序:分界元素,不是极值。 枢轴通常不在区间端点,也通常不是极值,所以指纹是:
存在某个位置
,使得 全部 、 全部 。
归并与希尔:连续段 vs 跳跃子序列。 归并的有序是连续的;希尔的有序是跳跃的。
基数排序:完整关键字可能仍然很乱。 LSD 每趟只按一位排序,中间状态里最大的数完全可能排在最前面(个位为 0 的话),这是它区别于所有比较类排序的特征。
考点速记
三条结论:
- 中间状态不是要背的清单,是不变量的可见形式——记住每种算法"一趟干了什么",状态自然推得出来。
- 共有特征要靠"下一层"细分:尾部就位是冒泡与堆排共有的,靠前部堆序细分;前缀有序是插入与选择共有的,靠"是不是全局最小"和"后缀有没有被碰过"细分。
- 排除与确认是不对称的:违反不变量
一定不是(可靠);符合特征 只能说可能是。
这一节在真题里被考过的形式(下方「真题练习」与《排序算法对比》共用同一批题):
- 给第
趟结果,问"只能是"哪种排序:先用"后缀有没有被碰过"筛掉插入类,再用"就位的是不是全局最值"分开选择与插入,最后用堆序分开冒泡与堆排。 - 给前几趟结果,问"可能是"哪种排序:判断标准同上,但要注意"可能"意味着只需存在一种合法解释。
- 判某序列不可能是快排第 2 趟结果:扫前缀最大值 + 后缀最小值,数就位元素够不够 2 个。
- 由希尔某趟结果反推增量:按
分组验证每组是否升序(详见希尔排序)。 - 由序列变化表判断算法:给"初始 / 第 1 趟 / 第 2 趟"三行,逐个候选算法套不变量。
- 建大根堆的序列变化过程选择题:看的是建堆次序(从
倒着来、一次下沉到底),详见堆排序。 - "每趟至少能确定一个元素最终位置"的有哪些:简单选择、快速、堆排三个(详见排序算法对比)。
易错:只看尾部分不出冒泡和堆排。 两者的尾部一模一样,必须检查前部堆序。
易错:插入类的指纹是"后缀没被碰过",不是"前缀有序"。 后者是插入与选择共有的。
易错:别用"元素位置有没有变动"排除基数排序。 存在一趟分配收集后位置完全不变的合法情形。
教材出处
- "使有序区中记录的数目增加一个或几个的操作称为一趟排序",以及插入类/交换类/选择类/归并类/分配类各自"如何扩大有序序列长度"的定义:严蔚敏《数据结构(C 语言版)》(第 2 版),p235
- 各算法逐趟中间状态的教材原例:直接插入排序过程(图 8.1,p237)、起泡排序过程(图 8.3,p242)、快速排序一趟划分与全过程(图 8.4,p244)、简单选择排序过程(图 8.6,p247)、堆排序过程(图 8.12,p253)、二路归并排序过程(图 8.13,p254)、希尔排序过程(图 8.2,p240)、链式基数排序的三趟分配与收集(图 8.15,p257–p258)
- 各算法不变量的推导见各自单篇的「教材出处」一节
相关知识
排序的基本概念(本篇全部不变量的源头)| 排序算法对比(本条目的另一半)| 直接插入排序|希尔排序|冒泡排序|快速排序|简单选择排序|堆排序|二路归并排序|基数排序