news 2026/9/7 18:48:33

从2010年408真题看快速排序:手推一趟划分的避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从2010年408真题看快速排序:手推一趟划分的避坑指南

如果你翻过408真题的排序部分,会发现快速排序几乎是选择题里的常驻嘉宾,2010年全国统考第10题就是典型代表。这类题看着简单,可我带过的学生里,能把“一趟划分”结果一次做对的不到一半。原因不是不懂原理,而是手推的时候被指针边界、等值元素、先动左还是先动右这些细节绊住了。今天就借这道真题,把快速排序从原理到手推、从代码到避坑完整过一遍,无论你是刚开始复习还是一轮强化结束,这篇都能帮你在排序选择题上少丢分。

1. 2010年真题考点定位:这道题到底在考什么

1.1 题型位置与考纲要求

2010年是全国计算机学科专业基础综合统考的第二个年头,数据结构部分的选择题第10题落在排序章节。题型是单项选择题,考法很直接:给一个初始关键字序列,让你判断“以第一个元素为基准,进行一趟快速排序后的结果”应该选哪一项。

搞清楚考纲要求很重要。408对排序这一章的要求不是“背结论”,而是“掌握基本思想、排序过程、时间复杂度和稳定性”。具体到快速排序,就是要做到三件事:第一,能手动模拟一趟划分过程;第二,能写出或者补全快排代码;第三,能说清楚最好、最坏、平均情况的时间复杂度以及为什么不稳定。很多同学复习排序爱背结论,选择题碰到模拟过程就现原形,这道10题就是用来筛掉“只看不练”的人的。

1.2 为什么快排是命题“钉子户”

排序算法那么多,为什么偏偏快排出镜率最高?道理很简单:冒泡、直接插入的过程太直观,推演起来不容易错;堆排序、归并排序的中间状态又太抽象,不适合出选择题。快速排序处在一个很微妙的位置——原理一听就懂,但手推一趟划分时到处都是坑,两个指针怎么移动、等值元素放哪边、基准最后落在哪个位置,稍微马虎就错。这种“看着会、推着错”的特性,决定了它是选择题的理想素材。

另外一个原因是,快排的思想可以被扩展出很多变式。比如利用一次划分求第k小元素、判断某个序列是快排第几趟的结果、补全partition函数缺失的代码等等。从2010年往后看,这些变式在408和各个院校的自命题里反复出现,所以吃透标准的一次划分过程,等于给这类题目都打了底。

2. 快速排序原理:先把“分治”掰开揉碎

2.1 一句话理解快排

快速排序做的事情可以压缩成一句话:选一个基准值,把比它小的放到左边,比它大的放到右边,然后对左右两个子区间重复同样的操作。

这个逻辑不陌生,跟整理书架很像。想象你面前有一排厚薄不一的书,你随手抽出一本当标准,让所有比它薄的书放到它左边,比它厚的放到它右边。做完这一步,那本“标准书”的位置就已经固定了——以后不管怎么整理,它都不会再挪地方。接下来只需要对左边那一堆和右边那一堆分别再抽一本标准书,重复同样的过程,直到每一堆都只剩一本书。

这里面藏着一个解所有“某趟快排结果”题的关键性质:一趟划分结束后,基准元素一定落在它最终该在的位置上。记住这句话,考场上能省下大量时间。

2.2 挖坑法和交换法到底什么区别

教材和网课里讲快排,会出现两套实现思路,一套叫挖坑法,一套叫交换法(也叫Hoare法)。408手推题目,我强烈建议你只用挖坑法,因为它每一步在干什么非常直观,不容易写乱。

挖坑法的过程是这样的:先把基准值pivot存到一个临时变量里,此时序列的第一个位置就是一个“坑”。然后从右往左找比pivot小的元素,找到就填到坑里,这个元素原来的位置变成新的坑;再从左往右找比pivot大的元素,找到就填到右边的坑里。两个指针交替往中间逼近,直到它们相遇,最后把pivot填进相遇的位置。

交换法则稍微不同:两个指针从两端出发,右指针找到比pivot小的、左指针找到比pivot大的,然后交换这两个元素,重复直到相遇,最后再把pivot换到相遇点。从最终效果看,两种方法得到的结果是一样的,但交换法的代码和手推容易把下标搞乱,所以在考场上我建议你固定用挖坑法。

提示:如果基准选的是第一个元素,那一定是右指针先动。因为第一个位置已经被挖成坑了,必须先从右边找一个元素来填,你要是先动左指针,那就是找一个比基准大的元素去填一个空的左边的坑,逻辑上完全不对。

2.3 等值元素与边界条件的底层逻辑

很多人搞不懂代码里为什么写>=<=,而不是><。这背后其实是个很实际的问题:当序列里存在和基准相等的元素时,如果只写>,右指针遇到等于基准的元素会继续往前走吗?会停住吗?如果两边都停住,就进入了死循环。

标准教材代码里,右指针用a[high] >= pivot作为继续左移的条件,意思是“只要当前元素不小于基准,就继续往左找”,遇到等于基准的元素直接跨过去;左指针用a[low] <= pivot作为继续右移的条件,遇到等于基准的元素也直接跨过去。这样一来,等于基准的元素会被左右指针均匀地分到两侧,两个指针都会稳定地向中间移动,不会卡死。

这个细节在408代码填空题里经常出现。给你一段挖坑法代码,中间挖掉一两个条件,让你从选项里选,很多同学就凭感觉乱填,填完自己都不知道为什么。你现在理解了“为什么要写成大于等于而不是大于”,以后再遇到这种题,一眼就能看穿出题人想考什么。

3. 真题核心过程还原:一趟快速排序手把手推

3.1 题干典型形态与初始序列

2010年这道第10题,不同回忆版本里具体的关键字序列可能有细微出入,但考察逻辑完全一致。下面用王道和严蔚敏教材里最经典的序列来完整还原手推过程,你把这套方法掌握了,不管真题里换成什么数字都能照做。

给定初始序列:

[ 49, 38, 65, 97, 76, 13, 27, 49 ]

要求:以第一个元素49为基准,写出进行一趟快速排序之后的序列。

先明确下标:序列长度为8,low指向下标0,high指向下标7,pivot取a[0] = 49

3.2 挖坑法六步完整推演

第一步:右指针往左找小于49的元素。

从high=7开始,a[7] = 49,它等于pivot,不满足“小于49”的条件,继续左移。a[6] = 27,27小于49,停。把27填入low指向的坑,也就是下标0的位置。此时序列变成:

[ 27, 38, 65, 97, 76, 13, 27, 49 ]

下标6的位置变成了新坑,high停留在6。

第二步:左指针往右找大于49的元素。

从low=0开始,a[1] = 38,不大于49,继续右移。a[2] = 65,65大于49,停。把65填入high指向的坑,也就是下标6的位置。此时序列变成:

[ 27, 38, 65, 97, 76, 13, 65, 49 ]

下标2的位置变成了新坑,low停留在2。

第三步:右指针往左找小于49的元素。

从high=6开始,a[5] = 13,13小于49,停。把13填入下标2的坑。此时序列变成:

[ 27, 38, 13, 97, 76, 13, 65, 49 ]

下标5变成新坑,high停留在5。

第四步:左指针往右找大于49的元素。

从low=2开始,a[3] = 97,97大于49,停。把97填入下标5的坑。此时序列变成:

[ 27, 38, 13, 97, 76, 97, 65, 49 ]

下标3变成新坑,low停留在3。

第五步:右指针继续往左找小于49的元素。

high从5开始左移,a[4] = 76,76大于49,继续;a[3] = 97,97也大于49,继续。此时high移动到3,low也是3,两个指针相遇,循环结束。

第六步:回填pivot。

把基准49填入low和high相遇的位置,也就是下标3。最终一趟快排后的序列为:

[ 27, 38, 13, 49, 76, 97, 65, 49 ]

我把每一步的状态整理成一张表,方便你对照检查:

步骤操作当前序列lowhigh
初始取pivot=4949, 38, 65, 97, 76, 13, 27, 4907
1high左移,27填入下标027, 38, 65, 97, 76, 13, 27, 4906
2low右移,65填入下标627, 38, 65, 97, 76, 13, 65, 4926
3high左移,13填入下标227, 38, 13, 97, 76, 13, 65, 4925
4low右移,97填入下标527, 38, 13, 97, 76, 97, 65, 4935
5指针相遇于下标327, 38, 13, 97, 76, 97, 65, 4933
6回填pivot到下标327, 38, 13, 49, 76, 97, 65, 4933

3.3 30秒验证法:如何快速检查答案

考场上你不会一步一步推六遍,也不需要在每个选项上都做完整手推。更快的方法是利用“基准最终位置”。

先把原始序列排好序看一遍:

[ 13, 27, 38, 49, 49, 65, 76, 97 ]

两个49中,无论哪个作为基准,一趟快排后基准49都应该停在最终位置——如果按稳定排序看,左49应该在排序后数组的第4个位置。所以一趟快排后的序列,第4位(下标3)必须是49。然后检查这个49左边的元素是不是都小于等于49,右边的元素是不是都大于等于49。

假设题目给你四个选项:

  • A. 13, 27, 38, 49, 49, 65, 76, 97 —— 这已经整体有序,是完整排序结果,不是一趟快排;
  • B. 27, 38, 13, 49, 76, 97, 65, 49 —— 下标3是49,左侧全小于49,右侧全大于等于49,是正确答案;
  • C. 38, 49, 65, 97, 76, 13, 27, 49 —— 基准49在第2位,但排序后49不可能停在第2位,排除;
  • D. 27, 38, 13, 76, 49, 97, 65, 49 —— 下标3不是49,排除。

这个验证法其实就是在用性质做题:一趟划分结束后,pivot一定落在最终位置,左侧全不大于它,右侧全不小于它。三个条件同时满足,基本就是正确答案。

4. 快速排序代码实现与“边界”记忆方法

4.1 C语言挖坑法标准实现

手推会了,代码也得能写。下面是408考试最常用的挖坑法C语言实现:

void QuickSort(int a[], int low, int high) { if (low < high) { int pivotpos = Partition(a, low, high); QuickSort(a, low, pivotpos - 1); QuickSort(a, pivotpos + 1, high); } } int Partition(int a[], int low, int high) { int 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; // 基准回填到相遇位置 return low; // 返回基准最终位置 }

注意循环条件里那个low < high是必须的。如果没有它,右指针在内层循环里可能一路减到比low还小,或者左指针一路加到越界。所有快排代码的边界错误,几乎都是因为漏写内层循环里的low < high

再注意等号的方向:右指针用>=,左指针用<=。写成><在全是相同元素的序列里会死循环,这个我在2.3小节已经解释过,属于408期末或统考填空的高频陷阱。

4.2 为什么工程里的快排都不“标准”

你翻Java的Arrays.sort、C++的std::sort,表面看都叫快排,但实现方式和教材代码差别很大。原因是标准快排有两个致命弱点:第一,待排序序列接近有序时,时间复杂度会退化到O(n²);第二,递归深度可能达到n,栈溢出风险高。

工程上的解法通常是三招:一是随机选基准,或者从首、中、尾三个位置取中间值当基准,避免每次选到最大或最小元素;二是当子区间长度小于某个阈值(比如16)时,改用插入排序,因为小规模数据插入排序的常数更小;三是用非递归的栈模拟代替递归,或者像C++的std::sort那样混合使用快排、堆排序和插入排序,最坏情况也能保持O(n log n)。

408考试不会要求你写这些变体,但理解它们的存在,能帮你更好地理解“为什么标准快排在有序序列上反而慢”这个经典考点。

4.3 快排思想的两个高频变式

变式一:求第k小元素。

利用partition的性质,每次划分后基准的位置就是它在有序序列中的最终位置。如果基准位置正好是k-1,那基准就是第k小元素;如果基准位置大于k-1,只需要在左半区间继续找;否则在右半区间找。平均时间复杂度是O(n),这也是408综合题喜欢考的“快排思想扩展”。

int FindKth(int a[], int low, int high, int k) { if (low <= high) { int pos = Partition(a, low, high); if (pos == k - 1) { return a[pos]; } else if (pos > k - 1) { return FindKth(a, low, pos - 1, k); } else { return FindKth(a, pos + 1, high, k); } } return -1; }

注意不要在递归里反复对全区间partition,那样复杂度会退化到O(n log n)甚至更高。这也是很多同学代码题拿不到满分的原因。

变式二:双轴快排。

Java的Arrays.sort对基本类型数组用的是双轴快排,选取两个基准,把序列分成三部分。这个了解一下就行,408不会要求手写。

5. 考场易错清单与复杂度速查

5.1 7个高频易错点

易错点错误示范正确理解
指针移动顺序基准在左,却先动左指针基准在第一个位置时,必须先动右指针
等号缺失右指针用>而不是>=遇到相等元素会卡住,极端情况死循环
混淆“趟”的概念以为一趟等于一层递归408语境里一趟通常指一次完整partition
忽视觉度稳定性认为快排属于稳定排序快排不稳定,等值元素可能互换位置
有序序列复杂度以为有序时最快每次基准都在极端位置,退化O(n²)
递归深度判断认为递归深度恒为log n最坏情况下递归深度等于n
忽略返回位置只写出排列结果,不写基准下标综合题里经常需要返回基准最终位置

这里重点说一下“一趟”这个词。408题目里说“一趟快速排序”,几乎都是指“一次划分过程”,也就是partition执行完,基准落到最终位置。不是指递归树上同一层的所有划分。有的同学把“一趟”理解成“两个子区间分别又做了一次划分”,那结果就对不上了。考试时如果题干没有额外说明,默认一趟就是一次partition。

5.2 排序算法复杂度对照表

这张表建议你考前自己默写一遍,不要只看不写:

排序算法平均时间最坏时间空间稳定性
直接插入O(n²)O(n²)O(1)稳定
冒泡排序O(n²)O(n²)O(1)稳定
简单选择O(n²)O(n²)O(1)不稳定
希尔排序O(n^1.3)O(n²)O(1)不稳定
快速排序O(n log n)O(n²)O(log n)~O(n)不稳定
堆排序O(n log n)O(n log n)O(1)不稳定
归并排序O(n log n)O(n log n)O(n)稳定

快速排序的空间复杂度记得分情况:平均情况下递归深度是log n,空间是O(log n);最坏情况下递归深度是n,空间是O(n)。选择题如果只说“快排空间复杂度是O(1)”,那是错的,它把递归栈的空间忘了。

6. 从2010到冲刺阶段:快排还能怎么考

6.1 408高频变式总结

第一类是概念判断。比如给你一个序列的前三趟排序结果,让你判断它是什么排序方法;或者给你几个排序过程描述,问哪个不可能是快排的中间状态。这种题考的是“每一趟partition后至少有一个元素到最终位置”的性质。

第二类是代码补全。挖掉partition函数中的几个关键语句,让你从选项中选择。高频挖空点就是内层while的边界条件、等号方向、指针移动语句。我前面为什么反复强调等号和low<high,因为这就是出题人的固定考点。

第三类是综合应用。比如在长度为n的数组里查找第k大元素,要求平均时间复杂度O(n);或者用快排思想把数组分成“小于基准、等于基准、大于基准”三部分。这些都建立在你能熟练写出partition的基础上。

6.2 最后一个月怎么复习快排

我的建议是三遍法。第一遍,不看任何资料,手推两个序列的全部排序过程,推完对照教材检查每一趟序列是否正确;第二遍,闭卷写完整快排代码,写完用几组边界数据测,比如空数组、单元素数组、全部相等的数组;第三遍,把复杂度和稳定性背下来,同时把快排和堆排序、归并排序的代码对比着记,防止混淆。

如果你用的是严蔚敏教材,重点看7.4节快排的代码;如果你用王道,重点刷课后选择题中“排序过程模拟”部分。湖科大教书的课程我看过,讲得细,适合第一轮打基础,但到了强化阶段,一定要自己动手推,不能只看课。

再多说一句,408真题里排序题不是考算法多花哨,而是考你在有限时间里手稳不稳。你要是能像条件反射一样写出那个partition循环,这类题就再也不会成为你的失分点。

我个人当年复习时,做2010年这类“一趟快排结果”题也栽过跟头——第一次手推时我把等于基准的第二个49放到了基准左边,结果和所有选项都对不上。后来我把所有“某趟结果判断”类题目统一用“基准最终位置检查法”来验证,再也没有错过。最后送大家一个小习惯:每次推完一趟划分,先别急着对答案,自己问一句“基准现在在不在它最终该在的位置”,这一个问题能帮你排查掉九成的手误。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 18:46:43

深入理解堆:从二叉堆到PriorityQueue的底层原理与工程实践

1. 先把“堆”这回事彻底掰开揉碎 聊PriorityQueue之前&#xff0c;必须先搞清楚一个特别容易被搞混的点&#xff1a;日常说的“堆”&#xff0c;和Java里那个 java.util.PriorityQueue &#xff0c;和C报错里“堆已损坏”的“堆”&#xff0c;以及JVM里“堆外内存”的“堆”…

作者头像 李华
网站建设 2026/9/7 18:45:12

AI工程化实战:Agent与Harness Engineering核心技术解析

1. 项目概述&#xff1a;Agent与Harness Engineering实战解析在AI工程化落地的过程中&#xff0c;Agent&#xff08;智能代理&#xff09;和Harness Engineering&#xff08;约束工程&#xff09;正在成为技术团队必须掌握的核心方法论。去年我们团队在构建客服自动化系统时&am…

作者头像 李华
网站建设 2026/9/7 18:41:53

用kiro为历史项目建立认知基线,让迭代从猜代码变成查代码

接手一个跑了四五年的老项目&#xff0c;第一件事不是赶紧加功能&#xff0c;而是先搞明白代码为什么长成今天这个样子。这事我以前全靠人肉——翻git log、看老文档、找还在职的同事问&#xff0c;能在一周内理出个大概都算运气好。后来我开始用kiro&#xff0c;它解决的核心问…

作者头像 李华