不瞒你说我第一次认真整理排序算法时差点被“稳定性”三个字劝退。当时我自认为背熟了结论“冒泡稳定、插入稳定、归并稳定、快排不稳定、堆排不稳定……”结果面试官淡淡补了一句“那你说说快排为什么不稳定给我构造一个反例。”我当场卡壳。后来我把这些结论逐条推了一遍才发现稳定性不是一堆要死记的“咒语”而是每种算法在“比较方式、交换范围、放置策略”上的必然结果。这篇文章就把排序算法的稳定性、时间复杂度和空间复杂度一次性讲透。标题里的“十二”和“八大”容易让人犯迷糊我索性按“经典八大”为主线再把计数、桶、基数这些常被扩展进全家桶的算法一起覆盖凑成十二种顺序梳理。适合正在复习算法笔试、准备面试、写课程总结的同学也适合日常写代码前随手翻一翻哪种场景必须用稳定排序哪种场景用不稳定排序代价不大表里有答案。1. 先把“八大”说清楚范围、分类与稳定性的意义1.1 八大排序到底指哪八种不同教材的口径其实有点差别。最常见的“八大经典排序”是冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、基数排序再往外扩展计数排序和桶排序也经常被拉进来凑成十种、十二种的都有。你如果细看一些考研教材可能还会看到“二分插入排序”“锦标赛排序”这些变体但它们大多是从某个核心算法派生出来的核心思想没跑出下面这几类。先建立两个分类维度会很有帮助。第一个维度是“基于比较”还是“非比较”基于比较的排序冒泡、选择、插入、希尔、归并、快排、堆排。这类算法的通用下限是 O(n log n)比较次数决定了性能上限。非比较排序计数、桶、基数。它们绕过了“两两比较”的限制才能在某些条件上做到 O(n) 级别的复杂度。第二个维度是“原地”还是“非原地”原地排序冒泡、选择、插入、希尔、堆排还有快排严格说是半原地递归栈不算辅助数组。它们的空间复杂度能做到 O(1)只是快排要算递归栈。非原地排序归并排序需要 O(n) 辅助数组计数、桶、基数更是摆明了用空间换时间。先把分类框架搭好后面推导每个算法的复杂度时就会顺很多。因为“是不是原地”“基于什么比较策略”直接决定了空间复杂度和稳定性这三件事在代码里本来就是连环的。1.2 稳定性到底在说什么稳定性的定义说穿了就一句话如果两个元素的值相同排序之后它们的相对位置不能变。打个比方排队按身高从矮到高排A同学和B同学身高完全一样进队伍之前A站在B前面排完队A还得站在B前面这才叫稳定排序。如果不稳定A可能因为某些交换操作被甩到B后面两个人的“先来后到”就被打乱了。为什么这个性质在工程里这么重要因为真实世界要排序的数据绝大多数不是一个孤立的整数而是一个对象。一次考试的成绩表里每个分数背后还挂着一个学号、一个姓名、一个班级。如果我要“先按总分降序总分相同再按学号升序”最优雅的做法不是写一个复杂的比较器而是分两步先用学号排序再用总分做一次稳定排序。第二趟稳定排序不会破坏第一趟已经排好的学号顺序。如果第二趟用的是不稳定排序总分相同的学生之间学号顺序就可能会乱结果就错了。所以稳定性不是排序算法里的“加分项”它直接影响多关键字排序的正确实现方式。你甚至可以把它理解为一种“可以分层叠加”的属性稳定排序可以安全地连续执行多次后面的排序只会在前面的有序区域里做局部微调而不稳定排序没有这个保证。1.3 一张总表先记住我把这几种排序的复杂度结论先集中列出来后面逐个解释。表中的 n 是元素个数k 是计数/桶排序的值域范围d 是基数排序的位数r 是基数大小排序算法平均时间复杂度最好情况最坏情况空间复杂度稳定性冒泡排序O(n²)O(n)O(n²)O(1)稳定选择排序O(n²)O(n²)O(n²)O(1)不稳定插入排序O(n²)O(n)O(n²)O(1)稳定希尔排序O(n^1.3) 左右依赖增量序列O(n²)O(1)不稳定归并排序O(n log n)O(n log n)O(n log n)O(n)稳定快速排序O(n log n)O(n log n)O(n²)O(log n)~O(n)不稳定堆排序O(n log n)O(n log n)O(n log n)O(1)不稳定计数排序O(n k)O(n k)O(n k)O(k)稳定桶排序O(n k)O(n)O(n²)O(n k)稳定依赖桶内实现基数排序O(d(n r))O(d(n r))O(d(n r))O(n r)稳定看到这张表大概率你会冒出两个问题为什么希尔排序的平均复杂度是一个带“左右”的含糊值为什么计数和基数排序说稳定但很多人的实现却写错了这些坑下文都会填上。现在先建立一个整体感觉稳定的算法大多是“相邻元素之间做比较交换”或者“有意识地保持相等元素顺序”不稳定的算法几乎都存在“跨越式交换”或者“先将元素扔进分组”的动作。2. 逐个拆解复杂度推导与稳定性归因2.1 冒泡、插入、选择三种 O(n²) 算法的分野三个最简单的 O(n²) 排序恰好给出了稳定与不稳定的典型样本。冒泡排序为什么稳定很多人说不出所以然。它的核心只有一条每次只比较相邻的两个元素如果左边的值严格大于右边才交换。这里的“严格大于”是稳定性的命门。因为相等的时候不交换相等的两个元素就永远不会因为“冒泡”而互相跨越。复杂度也好推外层循环走 n-1 趟内层比较次数逐趟减少总比较次数约 n²/2如果加上“本趟是否有交换”的标志位最好情况输入已经有序一趟扫描就结束降到 O(n)。最坏和平均都是 O(n²)。冒泡排序的空间占用是 O(1)属于最单纯的原地排序。插入排序也稳定它的逻辑更像人打扑克时理牌从第二张牌开始把当前牌从右往左找位置找到“第一个小于等于当前牌的位置”后把当前牌插到它后面。我特别强调一下这个细节查找插入位置时如果遇到相等元素应该继续向左找到最后一个相等元素的位置把新牌插到这些相等元素之后。这样一来后插入的牌总是排在同值老牌后面相对顺序当然不会反转。插入排序在接近有序的数据上非常快最好情况每一趟只需比较一次整体 O(n)平均和最坏是 O(n²)空间 O(1)。工程里的“小数组优化”很喜欢用它就是因为常数小、实现简单。选择排序的稳定性就栽了而且是稳定性的反面教材。它的策略是每一趟从头到尾扫描找出剩余元素里的最小值然后和第 i 个位置交换。问题出在“交换”这两个字上这个交换是跨越式的。我构造一个经典反例数组 [5a, 5b, 3]其中 5a 和 5b 数值相同但为了区分给它们加了编号最初 5a 在 5b 前面。第一趟找到最小值 3把它和下标 0 位置的 5a 交换。交换后数组变成 [3, 5b, 5a]你看5a 被甩到了 5b 后面相等元素的先后顺序被一次跨位置交换直接破坏。选择排序的时间复杂度没有“最好”一说因为无论输入什么它都得老老实实扫描完每一趟所以最好、平均、最坏都是 O(n²)空间 O(1)。如果问我从这三个算法里能提炼出什么规律我的回答是稳定与否要看这个算法“一次操作能够让元素移动多远”。冒泡和插入的移动基本是“一步一个脚印”的相邻操作选择排序却是“一个最小值直接飞过去占据别人座位”。观察下面更高级的算法你会发现这个规律依然成立。2.2 希尔排序聪明的分组打破了稳定性希尔排序是插入排序的改进版核心思想是“先让数组大致有序再做最后一遍插入排序”。它把相隔一定距离的元素分成一组组内做插入排序然后逐步缩小这个距离直到距离为 1。这个“距离”叫增量最常用的初始增量序列可能是 n/2、n/4……但不同增量序列对性能影响极大。为什么希尔排序不稳定我这么说你应该能理解同一轮分组里相等的元素如果被分在同一组组内插入排序虽然可以保持稳定但是下一轮增量变小后它们又被重新分组上一轮好不容易维持的先后顺序在新一轮跨越式插入里可能再次被打乱。更直接的情况是组内插入排序本身也是跨距离的插入一个元素可能一下子就插到好几个同值元素前面去了。所以希尔排序的稳定性从设计上就没有被当作目标。复杂度是希尔排序最让初学者头疼的地方因为它没有一个统一闭式结论。不同增量序列的复杂度差异很大有的增量序列最坏能到 O(n²)有的可以达到 O(n^1.5) 甚至 O(n log²n)。教材里常见的说法“希尔排序平均约 O(n^1.3)”其实是针对某些经验上表现良好的增量序列而言的。所以面试时我建议你回答“希尔排序的时间复杂度与增量序列强相关一般业界会提 O(n^1.3) 或 O(n^{4/3}) 作为经验值最坏可能退化到 O(n²)空间 O(1)。”这样说既准确也显得你不是背结论而是理解背后的依赖关系。2.3 归并排序稳定里最优雅的 n log n归并排序是“分治思想”的教科书实现把数组从中间一分为二递归排左半、排右半然后合并两个有序数组。复杂度推导用递推式T(n) 2T(n/2) O(n)展开后每一层的总合并代价都是 O(n)一共 log n 层所以总复杂度是 O(n log n)。因为它无论输入有序还是无序都必须走完分治和合并的完整流程所以最好、平均、最坏都是 O(n log n)。归并排序能稳定的关键在于合并操作的具体写法。假设有两个有序子数组 A 和 B合并时要把较小者放入结果数组。当 A 和 B 的当前元素相等时规定“先取左半部分的元素”。这个规定看似微小却保证了所有相等元素的相对顺序不被打乱。因为归并排序递归拆分时左右两半内部也遵守同样的规则相等元素从左半到右半的顺序就被连续地传递下去。这里有个面试官很爱挖的细节数组版归并排序的空间复杂度是 O(n)因为需要一个和原数组等长的临时数组但如果是链表归并因为可以通过修改 next 指针来合并辅助空间可以降到 O(1)只是递归调用栈还需要 O(log n)。很多人在笔试时直接写“归并排序空间 O(n)”不算错但如果你能补充一句“这是基于数组的实现链表版本可以做到常数辅助空间”会明显加分。工程上备受推崇的 TimSort 本质上就是归并排序的自然扩展它把数组里已有的有序片段直接利用起来稳定性遗传自归并排序所以像 Python 这类对对象排序稳定性有要求的语言底层才会放心地选它。2.4 快速排序平均快但最坏 O(n²)快速排序也是分治思想但它和归并排序的思路正好相反归并排序是“先递归后合并合并是重点”快速排序是“先划分后递归划分是重点”。每一趟选一个基准pivot把小于基准的元素放到左边大于基准的元素放到右边然后递归处理左右两侧。平均复杂度为什么是 O(n log n)因为如果每次划分都能把数组大概对半分递推式还是 T(n) 2T(n/2) O(n)结果同样是 O(n log n)。最好情况就是每次都恰好对半分。最坏情况呢如果每次选基准都选到最小或最大元素那么划分后一侧永远是空另一侧永远是 n-1递推式退化成 T(n) T(n-1) O(n)累加后就是 O(n²)。典型场景是数组已经有序而你固定选第一个元素作为基准。解决方式也很常规随机选基准、三数取中、或者在递归到小数组时切换到插入排序现代工程里的内省排序就是“快排 堆排 插入”的组合拳。快排为什么不稳定我在开头说过这是很多人背了结论但讲不出原因的地方。你盯着划分过程看当前指针扫描到一个比基准小的元素需要把它和前面某个较大元素交换这个交换是远距离的。逆序对一旦跨越了相等元素顺序就乱了。另外还有一个常见情况基准元素最终要被放到正确的位置如果数组里存在多个和基准值相等的元素基准的移动可能让同值元素互相错位。比如数组 [2a, 2b, 1, 1, 0]选 2a 当基准划分操作中 0 会跑到最前面2b 的位置和基准之间的相对顺序都可能改变。所以快排不是“想不想稳定”的问题而是它的核心交换动作天然无法保证稳定除非给元素加辅助下标但那已经不是原来的快排了。空间复杂度方面快排虽然没有额外的大数组但递归调用需要栈空间。平均情况下递归深度 O(log n)最坏情况下退化成 O(n)。所以你要是看到有人拍胸脯说“快排空间 O(1)”他多半是没把递归栈算进去。2.5 堆排序建堆的线性时间与长距离交换堆排序用最大堆来排序先把数组建成一个最大堆然后反复把堆顶元素和堆尾元素交换堆的大小减一再下沉调整。复杂度里的两个数字都值得单独推导。第一个数字是“建堆为什么是 O(n) 而不是 O(n log n)”直觉上把每个元素插入堆需要 O(log n)n 个元素就是 O(n log n)。但如果用自底向上建堆法每个节点下沉的代价和它的高度成正比。考虑一个完全二叉树第 h 层有 n / 2^(h1) 个节点每个最多下沉 h 层总代价 Σ h * n / 2^(h1) 这个级数收敛于一个常数倍 n所以建堆是 O(n)。第二个数字是“排序阶段为什么是 O(n log n)”每趟把堆顶和堆尾交换然后对堆顶做一次下沉下沉代价 O(log n)反复 n-1 次总计 O(n log n)。两部分相加整体最坏、平均、最好都是 O(n log n)。空间 O(1)因为你可以直接用原数组建堆反复交换也只需要一个临时变量。堆排序不稳定原因和选择排序一脉相承它本质上就是“选择排序的堆优化版”。每一趟从堆里“选择”一个最大值然后和当前堆尾交换。堆顶元素和堆尾元素相隔很远这一交换可能让大量同值元素的相对位置错乱。下沉调整也是大跨度的父子交换。所以堆排序从设计源头就继承了对稳定性“不管不顾”的特点它换来的好处是无论如何都能稳定在 O(n log n)没有快排那种最坏退化风险。2.6 计数、桶、基数非比较排序的空间换时间这三种排序和前面所有算法不在一个赛道它们用上了“值域”这个额外信息从而突破了 O(n log n) 的比较排序下限但也引入了对数据范围和空间的约束。计数排序的思路是扫描一遍数组统计每个值出现的次数再累加得到每个值在结果数组中的结束位置最后从后往前把元素填进输出数组。复杂度 O(n k)k 是值域范围最大值减最小值加一空间 O(k) 或者更准确地说是 O(n k)因为需要输出数组。计数排序可以做到稳定前提是“从后往前填充”这个实现细节必须写对从后往前遍历原数组每个元素放进它对应的最后一个空位这样相同元素的顺序就反过来了再正回来恰好保持原序。很多教程只给结论不强调这个细节导致很多人写出来的计数排序其实是不稳定的。桶排序是把元素按范围分进若干桶每个桶内再排序通常用插入排序最后按桶顺序收集结果。平均情况 O(n k)k 为桶个数最好情况接近 O(n)最坏情况所有元素全掉进一个桶退化成桶内排序的复杂度 O(n²)。空间 O(n k)。桶排序的稳定性有一个隐藏依赖只要桶内用的排序是稳定的并且收集时按桶的先后顺序收集整体就是稳定的。基数排序是竖着排从最低有效位到最高有效位轮流做多趟“按位排序”。复杂度 O(d(n r))d 是最大位数r 是基数比如十进制 r10二进制 r2字节 r256。空间 O(n r)。基数排序稳定性的关键是每一趟按位排序都必须采用稳定排序通常用计数排序来实现。如果某一趟用了不稳定排序后面再高的位也救不回来。所以你可以把基数排序看作“把稳定排序当零件循环使用 n 次”。3. 稳定性背后的设计逻辑与工程价值3.1 稳定与不稳定的根源把上面八个算法放在一起看稳定性的规律其实很朴素。稳定的算法比如冒泡、插入、归并、计数它们的共同点是“元素移动范围受限”冒泡和插入只在相邻位置移动归并在合并时有意识地先取左半计数排序通过从后往前填充维持顺序。不稳定的算法也存在共同点选择排序每次把一个远处的极值搬过来快排划分时指针扫描加远距离交换堆排序堆顶和堆尾直接交换希尔排序跨距离分组插入。所以当你遇到一个没学过的排序算法想快速判断它稳不稳定别急着背结论先看它的核心操作如果它的核心是“相邻比较交换”大概率稳定如果核心是“从远处挑一个元素放到当前位置”或者“把元素分成多组再重组”大概率不稳定。这套判断逻辑比任何口诀都可靠。3.2 多关键字排序稳定性的最强应用场景前面举的“总分相同按学号排”就是最典型的多关键字场景。我用一个更具体的例子说明整个流程。有四个学生甲总分 90学号 103乙总分 85学号 101丙总分 90学号 102丁总分 85学号 104要求是总分降序总分相同再按学号升序。第一步先按学号升序排得到乙(101)、丙(102)、甲(103)、丁(104)。第二步按总分降序做稳定排序。关键来了因为排序是稳定的总分同为 90 的甲和丙会维持第一步中“丙在甲前”的相对顺序。结果就是甲(103)、丙(102)、乙(101)、丁(104)完全正确。如果你在第二步用了不稳定排序甲和丙可能互换位置120 分可能变成 102 分对 103 分这就是线上 bug 的温床。正因为如此主流语言的标准库在处理对象排序时都会优先考虑稳定算法。比如 Java 对对象数组的排序底层走的是 TimSort归并排序变体对基本类型数组则用双基准快排C 里 sort 不保证稳定但 stable_sort 明确保证稳定。基本类型数组用不稳定排序没问题因为整数之间无法区分“哪个是原来的你”稳定与否没有业务含义对象数组则不同同值背后还有别的字段稳定有时候就是业务需求。3.3 把不稳定排序改成稳定可行吗这个问题很有意思面试里也偶尔会作为开放题出现。答案是理论上都可以但都有代价。最通用的改造方案是“给每个元素加一个原始下标作为次级关键字”。排序前先把 (value, originalIndex) 包成一个复合对象比较时先比 valuevalue 相等就比 originalIndex。这样一来任何排序算法从最终结果上看都是稳定的因为 equally valued 元素的原始下标天然有序。代价是额外空间 O(n)还可能因为包装对象而降低缓存友好性。如果不想包装对象也可以把原始下标塞进元素结构体的一个字段里本质上是一样的。另一种思路是改造算法本身。比如把快排改成“稳定快排”可以做两趟划分第一趟把所有小于等于基准的元素顺序放入临时数组的左端第二趟放大于基准的元素这就是个稳定划分但代价是引入 O(n) 辅助空间而且常数比普通快排大很多。改完之后你实际上又拿到了一个类似归并排序的空间效率快排在原地性上的优势就没了。所以工程上从来不这么干需要稳定性时直接选归并排序或它的自然版本而不是硬改快排。4. 实战配合选型、代码验证与记忆口诀4.1 工程里到底选谁不同场景有不同答案我给了一套自己常用的决策思路数据量很小比如几十个元素以内无脑插入排序。它常数极小在基本有序的数据上甚至接近 O(n)代码又简单。数据量中等明确要求稳定归并排序或者使用语言标准库里的稳定排序接口。数据量很大不在乎稳定性内存也紧张快排或内省排序。C 的 std::sort、Java 的基本类型排序都属于这类根据数据量自动切算法。数据是链表结构优先考虑归并排序。链表归并不需要随机访问辅助空间小实现也自然。数据是取值范围有限的整数计数排序如果位数固定不大基数排序也行。数据近似均匀分布桶排序可以接近 O(n)但如果分布极不均匀最坏会退化成 O(n²)使用前要评估。我没把“永远用快排”作为万能答案因为不稳定和 O(n²) 最坏退化都是真实风险。工程标准库里的内省排序之所以强就是因为它检测到递归深度过深时切成堆排序避免了快排的最坏退化再对小数组切插入排序综合了三种算法的优点。4.2 验证稳定性的一行测试思路写代码验证稳定性最笨也最可靠的方法是给每个元素预先绑一个“原始序号”排序后检查同值元素的序号是否仍然升序。我常用 Python 快速验证def check_stability(sorted_data, keylambda x: x[0]): # sorted_data 中每个元素形如 (value, original_index) for i in range(len(sorted_data) - 1): if key(sorted_data[i]) key(sorted_data[i 1]): if sorted_data[i][1] sorted_data[i 1][1]: return False return True比如验证快排是否稳定可以生成一批 (value, index) 对调用排序后再用这个函数检查。注意这里排序的比较函数必须只比较 value不要把原始序号带进去否则验证就失真了。另一个小技巧是打印排序前后的 index 序列变化肉眼对比哪组等值元素的顺序被翻转过。这个方法在调试“自己实现的排序算法”时尤其好用。我有一次写基数排序自测大量随机数都通过但程序一上线就偶发顺序错乱结果就是用这个带索引的验证函数找到了某一趟按位排序里漏写了稳定性细节。排序算法这东西大量随机数据容易掩盖稳定性问题因为随机数据里等值元素本身就少你根本观察不到顺序变化。4.3 高频面试问题的回答模板面试里关于排序算法的问题可以总结成三类。第一类直接问复杂度“堆排序建堆复杂度为什么是 O(n)”这时别只背公式可以画一棵完全二叉树解释每层节点数乘以该层高度之和收敛到常数。第二类问稳定性“希尔排序稳定吗为什么不稳定”最好能给出一个具体反例哪怕是口头描述“一组相等的元素在缩小增量后可能被重新分到不同的组分组的重排会打乱原有相对顺序”也比干说“不稳定”强得多。第三类问工程选型“给你一百万个对象要求稳定排序内存有限你选什么”这种问题要综合考虑时间、空间、稳定性我的回答通常是“优先用归并排序变体如果内存实在紧张再讨论用代价换空间”。我再给一个快问快答样例可以直接背住问题我的回答冒泡排序稳定吗稳定因为只相邻比较交换相等不交换选择排序稳定吗不稳定每趟把远处的最小值交换到前面可能跨越等值元素插入排序稳定吗稳定新元素插入到等值元素后面即可实现时注意等号处理归并排序稳定吗稳定合并时左半优先快排稳定吗不稳定划分为核心的交换是跨距离操作堆排序稳定吗不稳定堆顶堆尾交换是典型的长距离交换计数排序稳定吗可以实现为稳定必须从后往前填充输出数组基数排序稳定吗稳定前提是每一趟按位排序都使用稳定算法5. 常见误区与避坑清单5.1 空间复杂度最容易被记错的三个点第一个是快排的空间复杂度。很多人以为快排是原地排序所以空间是 O(1)但递归栈也算空间平均 O(log n)最坏 O(n)。说“快排空间 O(1)”在严格定义下是错的。第二个是归并排序的空间。数组归并 O(n) 辅助数组但如果加上递归栈其实应该是 O(n log n)数量级还是 O(n)。链表归并则可以通过修改指针做到 O(1) 辅助空间这个结论需要在卷面上写清楚前提。第三个是堆排序。迭代实现的空间确实是 O(1)但如果写了递归实现递归栈会是 O(log n)虽然堆排序一般没人写递归。5.2 复杂度描述里的隐藏前提希尔排序的复杂度是“增量序列依赖型”最怕有人把它当成一个确定值。不同资料给出的 O(n^1.3)、O(n^{4/3})、O(n log²n) 都是针对特定增量序列的有些增量序列最坏仍然是 O(n²)。计数排序里的 k 是值域范围不是数组长度。当 k 很大比如有 10 个元素但值域是 0 到 10^9那计数排序的时空开销都爆炸这时候用快排反而好。基数排序的 d 和 r 也要解释清楚r 是每趟的“桶数”进制数d 是趟数复杂度 O(d(n r))当 d 是常数时可以近似 O(n)但 d 本身随数值范围增长。5.3 记忆口诀与自测清单背结论这件事我不能完全否定因为考试和面试的确需要快速反应。但我的建议是用口诀做“第一反应”用“原因推导”做“第二防线”。我自己的记忆锚点是“插冒归——稳定选希快堆——不稳定”基数、计数、桶如果实现正确也是稳定。你甚至可以这样记忆稳定排序的英文 Stable Sort可以拆成 St Sorted Table想一想“表格排序需要保持行顺序”就记起来了。自测的时候别只对着表默写试着解决这几个问题给选择排序构造一个不稳定反例答案两个相等元素加一个更小的元素那个小元素会把前面那个等值元素交换走。解释归并排序合并时为什么要写“”而不是“”。答案写 会把相等的右半元素提前破坏稳定性解释为什么说“堆排序是选择排序的亲戚”。答案都是每次从剩余元素里挑一个最大/最小放到最终位置只是堆用完全二叉树加速了挑选过程说出快排最坏 O(n²) 的典型触发条件以及至少两种缓解方案。随机基准、三数取中、切换到内省排序做完这组自测你对排序算法的理解就不只是“背了张表”而是真正能把稳定性、复杂度、工程选型串起来用了。最后讲一个我自己的小习惯每次写完一个排序算法我都会顺手生成一组带原始索引的测试数据跑一遍稳定性检查这几乎成了肌肉记忆。别嫌这一步麻烦排序是那种“看起来全对一凑到等值元素就翻车”的领域。你现在花十分钟把这点验证逻辑想清楚以后调试线上排序逻辑的时候能少掉一大半头发。
阅读完成 · 觉得有帮助?