ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

数据结构必学:七大排序算法图解与复杂度对比,期末考与面试实战选型指南

数据结构必学:七大排序算法图解与复杂度对比,期末考与面试实战选型指南 我读数据结构那会儿最头疼的不是链表也不是树恰恰是排序算法。教材上一口气摆出冒泡、选择、插入、希尔、归并、快排、堆七种名字个个认识一合上书就只剩“快排好像很厉害”这一个模糊印象。后来准备期末考试、再准备考研复试和面试我把这七个排序反复画了几十遍才算真正摸到它们的门道。这篇文章就是把我的图解思路和踩过的坑一次性整理出来。内容围绕数据结构中的七大排序算法展开讲清楚每一类算法“每一趟到底做了什么”再给一份时间、空间、稳定性对照表最后说说期末考、408统考和真实项目里到底怎么选。适合正在学数据结构的学生、准备复习的考研人也适合想快速捡起排序知识点的开发者。接下来我们直接进入正题按我自己的理解方式把七个排序分成三组来拆。1. 排序算法到底在解决什么问题先建立全局观1.1 我为什么把七大排序划分成三组很多人学排序失败是因为把七个算法当成七个孤立的代码片段在背。代码背下来了题目稍微变形就不会做。我后来发现一个更高效的方式先把七个算法按“核心思路”分组。第一组是 O(n²) 组冒泡、选择、插入。它们逻辑直白、好理解代价是慢。它们代表的是“最朴素”的排序思维一趟一趟地把元素放到它该去的位置。第二组是突破组希尔、归并。希尔是给插入排序套上一层“分组”外衣归并是彻底换思路用分治把复杂度压到 O(nlogn)。它们代表两种不同的破局方式一个靠优化移动步长一个靠递归分解问题。第三组是快排和堆排序。快排也属于分治家族但它和归并走的路线完全相反——归并是“先分再合”的合并思维快排是“先划再递”的划分思维。堆排序则彻底改变了看待数组的方式把一维数组当成完全二叉树去调整。这样一组你会发现七个算法背后只有三四种思维模型学起来就不再是零散记忆而是成体系的对比。我个人强烈建议你也按这个思路整理自己的笔记尤其是期末复习的时候这个框架能帮你省下大量时间。1.2 衡量一个排序算法的四把尺子排序算法不能只问“快不快”更准确的问题是在什么情况下快占多少额外空间稳不稳定适不适合当前场景这四件事几乎是所有排序问题的核心。时间复杂度至少要分清最好、平均、最坏三种情况。比如快排平均 O(nlogn)但有序输入下可能退化到 O(n²)。空间复杂度分原地和非原地。原地排序基本只用常数额外空间归并需要 O(n) 的辅助数组快排虽然不需要辅助数组但递归调用有栈开销。稳定性相等元素排序后相对顺序是否保持不变。数据库里做多关键字排序时稳定性非常关键。应用场景数据量多大、是否接近有序、是否要求稳定、内存是否受限。没有完美的排序算法只有最匹配当前需求的算法。这四把尺子最后会汇总成第6章那张表。但提前说一句真实项目里选排序本质上是在“四把尺子”里做取舍。你要速度可能就得放弃稳定性你要稳定性可能就得接受 O(n) 空间。这一点想通了再看各种排序算法的优缺点会通透很多。2. 冒泡、选择、插入O(n²)三兄弟的图解与对比2.1 冒泡排序相邻比试“大鱼”沉底冒泡排序的思路非常简单从头到尾依次比较相邻元素如果前一个比后一个大就交换。一趟下来最大的元素就像气泡一样浮到了数组末尾。之后每趟都忽略已经排好的末尾元素重复 n-1 趟就能全部排完。用数组 [5, 1, 4, 2, 8] 走一遍初始: [5, 1, 4, 2, 8] 第1趟: (5,1)交换 → [1, 5, 4, 2, 8] (5,4)交换 → [1, 4, 5, 2, 8] (5,2)交换 → [1, 4, 2, 5, 8] (5,8)不换 → [1, 4, 2, 5, 8] 第2趟: (1,4)不换 → [1, 4, 2, 5, 8] (4,2)交换 → [1, 2, 4, 5, 8] (4,5)不换 → [1, 2, 4, 5, 8] 第3趟: 无交换提前结束C语言实现void bubbleSort(int arr[], int n) { for (int i 0; i n - 1; i) { int swapped 0; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; swapped 1; } } if (!swapped) break; // 没有交换说明已经有序 } }我当初学冒泡时最容易忽略一个点它是稳定的。为什么因为代码里只有当arr[j] arr[j1]时才交换等于的时候不搬动所以相等元素的相对顺序永远不会变。冒泡还能通过swapped标志把最好情况提升到 O(n)这在基础排序里很罕见但代价是平均和最坏情况仍然是 O(n²)。现在几乎没有人拿它做正经排序它的价值在于帮你建立“交换排序”的基本概念。2.2 选择排序每趟挑最值直接上岗选择排序的思路是第 i 趟在未排序区间里找出最小值把它和第 i 个元素交换。这样每一趟只做一次交换元素直接去最终位置不需要像冒泡那样一路“拱”过去。还是看一组图解数组 [64, 25, 12, 22, 11]初始: [64, 25, 12, 22, 11] 第1趟: 全区间最小是11与64交换 → [11, 25, 12, 22, 64] 第2趟: 剩余区间最小是12与25交换 → [11, 12, 25, 22, 64] 第3趟: 剩余区间最小是22与25交换 → [11, 12, 22, 25, 64]代码void selectionSort(int arr[], int n) { for (int i 0; i n - 1; i) { int minIdx i; for (int j i 1; j n; j) { if (arr[j] arr[minIdx]) minIdx j; } if (minIdx ! i) { int tmp arr[i]; arr[i] arr[minIdx]; arr[minIdx] tmp; } } }选择排序有个反直觉的点不管输入已经多有序比较次数始终是 n(n-1)/2所以最好、平均、最坏都是 O(n²)。它唯一的优点是交换次数少只有 n-1 次在“交换成本极高”的场景下比冒泡有优势。稳定性是它的重灾区。我举个例子你就明白了数组 [5, 8, 5, 2]第一趟选出最小值 2把它和第一个 5 交换数组变成 [2, 8, 5, 5]。原来的两个 5前面的跑到了后面相对顺序被破坏所以选择排序不稳定。我考试时给自己总结了一条经验凡是大跨度交换的排序大概率不稳定后面讲快排和堆排序你会发现这条经验很管用。2.3 插入排序像整理扑克牌一样自然插入排序的思路你在打牌时其实天天用每拿到一张新牌把它插到手里已经排好序的牌中的正确位置。在代码里我们从数组第二个元素开始将它作为“待插入牌”和前面有序部分的元素逐一比较并后移找到合适位置插入。数组 [5, 2, 4, 6, 1, 3] 的完整过程i1: key25后移插入 → [2, 5, 4, 6, 1, 3] i2: key45后移2不动插入 → [2, 4, 5, 6, 1, 3] i3: key6已在正确位置 → [2, 4, 5, 6, 1, 3] i4: key1一路换到最前 → [1, 2, 4, 5, 6, 3] i5: key3插入到2和4之间 → [1, 2, 3, 4, 5, 6]代码void insertionSort(int arr[], int n) { for (int i 1; i n; i) { int key arr[i]; int j i - 1; while (j 0 arr[j] key) { arr[j 1] arr[j]; j--; } arr[j 1] key; } }插入排序有两个非常值钱的特性。第一它是自适应的如果数组本来就接近有序内层 while 几乎不会执行最好情况能达到 O(n)。第二它稳定只有当arr[j] key时才后移等于时不移动所以相等的牌保持着原来的顺序。最容易被低估的是它的实战地位。很多工程代码在排序递归切到很小区间时会直接切到插入排序因为小区间内插入排序的实际速度往往快过继续递归的“大材小用”。这一点后面讲快排优化时还会再提。三种基础排序的对比表算法最好平均最坏空间稳定性冒泡排序O(n)O(n²)O(n²)O(1)稳定选择排序O(n²)O(n²)O(n²)O(1)不稳定插入排序O(n)O(n²)O(n²)O(1)稳定3. 希尔排序与归并排序冲破O(n²)的两种绝活3.1 希尔排序让相隔最远的元素先握手插入排序的痛点是什么数据只能一个位置一个位置地挪。如果最小的元素在数组最末尾想让它到开头得经过 n-1 次移动效率很低。希尔排序的思路就是先按一个间隔 gap 把数组分成若干组组内做插入排序让元素可以“跳着”移动然后不断缩小 gap直到 gap1最后做一次普通插入排序收尾。用 [9, 8, 7, 6, 5, 4, 3, 2, 1] 演示初始 gap4分组(下标差为4): 下标0,4,8: 9,5,1 → 插入排序 → 1,5,9 下标1,5: 8,4 → 插入排序 → 4,8 下标2,6: 7,3 → 插入排序 → 3,7 下标3,7: 6,2 → 插入排序 → 2,6 gap4 后数组变为: [1, 4, 3, 2, 5, 8, 7, 6, 9] gap2 分组: 下标0,2,4,6,8: 1,3,5,7,9 → 已有序 下标1,3,5,7: 4,2,8,6 → 插入排序 → 2,4,6,8 数组变为: [1, 2, 3, 4, 5, 6, 7, 8, 9] gap1: 最后做一次普通插入排序因为已经很接近有序速度非常快代码实现void shellSort(int arr[], int n) { for (int gap n / 2; gap 0; gap / 2) { for (int i gap; i n; i) { int key arr[i]; int j i - gap; while (j 0 arr[j] key) { arr[j gap] arr[j]; j - gap; } arr[j gap] key; } } }这里要注意内层循环从i gap扫到末尾看起来不是“按分组分别处理”但实际效果等价于对每个 gap 间隔的子序列分别做插入排序只是写起来更简洁。这个写法我第一次看的时候绕了半天后来自己手推一遍才理解。希尔排序的复杂度很难精确描述它取决于增量序列的选择。常见教学用 gapn/2 逐次减半平均复杂度大约在 O(n^1.3) 到 O(n^1.5) 之间最坏可以到 O(n²)。408 一般不强求精确推导你能说出“和增量序列有关、通常优于 O(n²)”就够了。稳定性方面希尔排序是不稳定的而且这是个高频考点。很多人误以为“组内用的是插入排序插入排序稳定所以希尔排序也稳定”这是错的。不同分组的元素经过跨越移动后相等元素的相对顺序可能被打乱。我特别强调这一点因为这是期末考试选择题里非常经典的陷阱。3.2 归并排序分治思想的满分答卷归并排序的思路一句话就能说清把数组从中间劈成两半分别排序再把两个有序的子数组合并成一个有序数组。递归地执行这个过程直到子数组只剩一个元素。看一个经典图解数组 [38, 27, 43, 3, 9, 82, 10]分解: [38, 27, 43, 3, 9, 82, 10] [38, 27, 43, 3] [9, 82, 10] [38, 27] [43, 3] [9, 82] [10] [38] [27] [43] [3] [9] [82] [10] 合并: [27, 38] [3, 43] [9, 82] [10] [3, 27, 38, 43] [9, 10, 82] [3, 9, 10, 27, 38, 43, 82]归并排序的核心动作在 merge 阶段它需要一块临时数组用双指针不断比较两个有序子数组的头部元素把较小的放入临时数组。C语言实现void merge(int arr[], int left, int mid, int right) { int len right - left 1; int temp[len]; int i left, j mid 1, k 0; while (i mid j right) { temp[k] (arr[i] arr[j]) ? arr[i] : arr[j]; } while (i mid) temp[k] arr[i]; while (j right) temp[k] arr[j]; for (i 0; i len; i) { arr[left i] temp[i]; } } void mergeSort(int arr[], int left, int right) { if (left right) return; int mid left (right - left) / 2; mergeSort(arr, left, mid); mergeSort(arr, mid 1, right); merge(arr, left, mid, right); }归并排序最值得记住的两点是第一它最坏情况也是 O(nlogn)不存在快排那种退化风险第二它稳定。稳定性的关键在于合并时用的是arr[i] arr[j]相等时优先取左边数组的元素所以相同的元素在合并后仍然保持原先的相对顺序。这里的是代码细节但也是考试常挖的坑如果你写成归并排序就不再稳定。代价也很明显需要 O(n) 的额外空间不是原地排序。递归调用还有栈空间开销所以它比快排吃内存。但在需要稳定排序、数据规模很大甚至超出内存的场景下归并几乎是不可替代的。3.3 两个“破局者”到底该怎么用希尔排序在真实工业项目里用得非常少因为增量序列不好选常数项也不占优它的主要价值在教学和理论层面帮助你理解“预排序 插入排序”的组合思想。但学过它之后你会更容易理解为什么很多高性能排序算法会在最后一步切到插入排序——数据接近有序时插入排序真的太香了。归并排序则是工程上的常客。Java 的Collections.sort对对象排序默认用归并的思路链表的排序也非常适合归并因为不需要频繁移动元素只需要调整指针。外部排序处理“数据量超过内存”的场景时归并更是核心思路把大文件切成能装进内存的块分别排好序再不断归并成更大的有序块。一句话总结两个破局者希尔是“思路启发者”归并是“实战硬通货”。4. 快速排序图解枢轴挖坑与递归分割4.1 一趟“挖坑法”划分是怎么完成的快排的思路和归并正好相反。归并是先把数组拆碎再靠合并排序快排是先选一个枢轴 pivot把数组分成“左小右大”两部分枢轴自己直接落到最终位置然后递归排左右两边。快排的 partition 有多种写法考试里最常用的是“挖坑法”。我用数组 [5, 3, 8, 1, 2, 7, 6, 4] 走一遍选第一个元素 5 当枢轴初始: [5, 3, 8, 1, 2, 7, 6, 4]low0, high7, pivot5 第1步: 从high向左找比5小的数找到4填到low位置的坑 [4, 3, 8, 1, 2, 7, 6, _] 第2步: 从low向右找比5大的数找到8填到high位置的坑 [4, 3, _, 1, 2, 7, 6, 8] 第3步: 从high向左找比5小的数找到2填到low位置的坑 [4, 3, 2, 1, _, 7, 6, 8] 第4步: 从low向右找比5大的数low走到1、2、3、4 在索引4处与high相遇停止移动 把pivot5填到相遇位置: [4, 3, 2, 1, 5, 7, 6, 8]这一趟结束后5 的左边全是小于 5 的数右边全是大于 5 的数5 自己已经在最终位置。接下来只需要对 [4, 3, 2, 1] 和 [7, 6, 8] 递归执行相同过程即可。C语言实现int partition(int arr[], int low, int high) { int pivot arr[low]; while (low high) { while (low high arr[high] pivot) high--; arr[low] arr[high]; while (low high arr[low] pivot) low; arr[high] arr[low]; } arr[low] pivot; return low; } void quickSort(int arr[], int low, int high) { if (low high) return; int p partition(arr, low, high); quickSort(arr, low, p - 1); quickSort(arr, p 1, high); }这里最需要注意的是循环里的和。它们决定了当数组存在重复元素时指针是怎么移动的。我建议你亲自用包含重复元素的数组走一遍比看十遍书更有用。快排的平均复杂度是 O(nlogn)递归栈的空间是 O(logn)。听上去很完美但它有一个致命弱点。4.2 快排最怕“有序”退化场景与三个补救办法快排最坏情况发生在每次划分都极度不平衡的时候最典型的触发场景是数组已经有序或逆序而且你每次都选第一个元素当枢轴。此时每趟划分只能把规模减少 1递归深度变成 n时间复杂度直接退化成 O(n²)。我当年第一次亲手模拟这个场景时内心是崩溃的一个平均 O(nlogn) 的算法就这么被“有序输入”废掉了。实际工程里有三种常规补救手段面试时能说出这三点很加分第一随机选枢轴。在 partition 之前把 arr[low] 和某个随机位置的元素交换让最坏情况变成一个极低概率事件而不是稳定复现。随机化的成本极低收益极其明显。第二三数取中。比较区间首、中、尾三个元素取其中位数作为枢轴。这个策略对“基本有序”的数据效果立竿见影能大幅减少划分不平衡的概率。我在手写代码时也喜欢用它因为逻辑简单、不需要额外随机数。第三小区间切换插入排序。快排递归到最后区间已经很小时递归调用的开销反而成为主要成本。这时直接对小区间调用插入排序既能减少递归深度又能利用插入排序在近有序数据上的天然优势。很多工业级排序库的实现都包含这个策略。还有一个进阶操作值得一提当数组里大量重复元素时普通 partition 会让重复元素反复参与比较性能照样退化。这时候可以用三向切分也叫荷兰国旗问题把数组分成“小于枢轴、等于枢轴、大于枢轴”三段只递归处理不等于枢轴的部分。这个思想稍微超出期末范围但面试问到“大量重复元素如何优化快排”时你能说出来就非常加分。稳定性方面快排是不稳定的。partition 过程中大跨度交换很容易让相等元素的相对顺序翻转。考选择题时如果遇到“以下哪个排序是稳定的”快排通常不会被列在稳定那一栏。5. 堆排序图解把数组看成完全二叉树的神操作5.1 建堆到输出的两步走堆排序的思路很特别它不把数组当普通数组看而是当作一棵完全二叉树来看。下标 i 的父节点、左孩子、右孩子之间存在固定映射关系左孩子是 2i1右孩子是 2i2最后一个非叶子节点的下标是 n/2-1。堆排序分两大阶段第一阶段把数组建成大顶堆父节点值大于等于子节点值此时堆顶就是全数组最大值第二阶段反复把堆顶和末尾元素交换让当前最大值归位再缩小堆的范围重新调整堆。不断重复直到堆里只剩一个元素。我用数组 [4, 10, 3, 5, 1] 演示建堆过程它对应的完全二叉树如下4 / \ 10 3 / \ 5 1从最后一个非叶子节点 n/2-11值为10开始向下调整。10 已经大于它的孩子 5 和 1不需要动。然后调整下标 0 的节点节点4的左右孩子是10和310更大交换4和10: 10 / \ 4 3 / \ 5 1 4继续下沉比较它的左右孩子5和15更大交换4和5: 10 / \ 5 3 / \ 4 1 建堆完成数组变为: [10, 5, 3, 4, 1]建堆完成后进入排序阶段。把堆顶 10 和数组末尾 1 交换最大值 10 归位然后对前 4 个元素重新调整成大顶堆。重复这个过程数组就会从后往前逐步变成有序。每次交换后都必须重新 heapify这是手算堆排序最容易漏掉的一步。C语言实现void heapify(int arr[], int n, int i) { int largest i; int l 2 * i 1; int r 2 * i 2; if (l n arr[l] arr[largest]) largest l; if (r n arr[r] arr[largest]) largest r; if (largest ! i) { int tmp arr[i]; arr[i] arr[largest]; arr[largest] tmp; heapify(arr, n, largest); } } void heapSort(int arr[], int n) { for (int i n / 2 - 1; i 0; i--) { heapify(arr, n, i); } for (int i n - 1; i 0; i--) { int tmp arr[0]; arr[0] arr[i]; arr[i] tmp; heapify(arr, i, 0); } }你可能会问为什么 i 从 n/2-1 而不是 n-1 开始因为所有叶子节点本身已经满足堆的性质不需要调整。只有从最后一个非叶子节点往上走才需要逐个下沉。5.2 建堆为什么快堆排序又为什么“高不成低不就”很多人以为建堆需要对每个节点都做 O(logn) 的下沉调整所以建堆复杂度是 O(nlogn)。这个直觉是错的。实际上从最后一个非叶子节点开始向上调整时大部分节点位于二叉树的较底层它们能下沉的高度非常有限。把所有节点下沉的总工作量加起来数学上的结论是 O(n)而不是 O(nlogn)。这个结论 408 不一定要求你证明但选择题偶尔会直接考你可以把这个“线性建堆”结论直接记下来。堆排序整体复杂度是稳定的 O(nlogn)不管输入已经有序还是完全逆序它都没有快排那种最坏退化问题。空间上是原地排序不占额外数组这是它相对归并排序的巨大优势。但它有个尴尬的点常数项比较大而且堆顶元素与末尾交换后heapify 要跨越访问数组的不同区域缓存局部性很差所以在绝大多数常规场景下堆排序跑不过快排。不过有一个场景它特别值钱只需要找前 K 大或第 K 大的元素。用大小为 K 的小顶堆扫一遍全量数据堆顶就是当前第 K 大的候选值复杂度只有 O(nlogK)完全不需要把所有数据都排好序。我在准备面试时被问到过好几回 TopK 问题底层的核心数据结构就是它。堆排序不稳定这个要专门记住。堆顶和末尾的交换、建堆过程中的下沉调整都可能让相同元素的相对位置发生变化。所以如果题目要求“稳定且 O(nlogn)”答案是归并排序而不是堆排序。6. 七大排序横向对比从复杂度表到实战选型6.1 一张表背下所有关键结论现在把七个排序的所有关键指标汇总成一张表这是我复习时反复默写的内容。建议你也把它抄下来贴在手边排序算法最好平均最坏空间稳定性冒泡排序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(nlogn)O(nlogn)O(nlogn)O(n)稳定快速排序O(nlogn)O(nlogn)O(n²)O(logn)不稳定堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定看这张表有几点值得额外说明。冒泡最好情况 O(n) 是加了提前退出标志才成立的选择排序无论什么输入都是 O(n²)因为它每趟都要完整扫描剩余区间找最小值快排空间 O(logn) 是递归栈的开销不是说它完全不用额外空间。希尔排序的平均复杂度在大纲里一般不做精确要求因为实际表现严重依赖增量序列的选取你能说出“约 O(n^1.3) 且与增量序列有关”就足够了。6.2 真实项目里如何选排序三个典型决策考试背表实战要拍板。我根据自己的项目经验总结了几个常见决策场景。场景一数据量很小比如几十个元素或者数据已经非常接近有序。直接选插入排序。道理很简单插入排序在这种输入下几乎不怎么移动元素实际执行速度往往比快排还快。很多标准库的排序实现里递归到小区间就切插入排序也是这个原因。场景二数据量大、随机分布、不需要稳定。默认选快排但一定要随机化枢轴或者三数取中防止有序输入触发最坏情况。快排在真实硬件上的平均表现是这些 O(nlogn) 算法里最好的这也是绝大多数库函数C 的 qsort、C 的 sort都以它为基础的原因。场景三必须稳定或者数据量大于内存。选归并排序。稳定排序几乎只能归并顶上来外部排序更是归并的主场。另一种选择是用归并排序处理链表因为它不需要 O(n) 的索引随机访问。还有第四个常见场景内存极紧张比如嵌入式环境且不在乎稳定性。这时候堆排序能原地完成 O(nlogn)不占额外空间是最合适的选择之一。我在项目里很少直接手写这些排序因为语言标准库通常已经做过优化。但理解它们的选型逻辑能帮你在面对“为什么这里性能不对”“为什么这个库排序结果不稳定”之类问题时快速定位这种底子功夫迟早派得上用场。6.3 408和期末考最爱挖的四个坑最后整理几个经典考点都是我或者我同学曾经错过的地方。坑一快排第一趟结果判断题。题目给你几个候选序列问哪个不可能是第一趟快排后的结果。这类题看一个关键特征第一趟结束后枢轴元素一定处于最终位置并且它左边的元素全部小于它、右边的元素全部大于它。用这个性质逐项排除就行不需要完整模拟整个过程。坑二稳定性判断题。我有个土办法如果排序过程中发生了跨越多个位置的交换大概率不稳定比如选择、快排、堆如果只在相邻位置交换或后移大概率稳定比如冒泡、插入。归并稳定性靠合并时用保证这个特殊情况需要单独记住。很多同学容易记混用这套推理逻辑代替死记硬背会稳得多。坑三手算堆排序输出序列。这个问题几乎每年都有人栽。每输出一个堆顶元素后必须把末尾元素换上来再对缩小后的堆重新 heapify。漏掉任何一次调整后面的序列全错。我建议你在草稿纸上把每一轮交换前的二叉树画出来亲眼看一遍比脑子里空想要稳。坑四比较次数和移动次数混淆。选择排序比较次数多但移动次数少每趟最多一次交换插入排序在近有序时比较和移动都很少而冒泡在两种计数上都不占优。题目如果问“移动次数最少的排序是谁”答案往往是选择排序或插入排序这点要注意区分。这些坑看起来零散背后其实都是“你到底有没有亲手模拟过一遍算法过程”。每一趟交换、每一次下沉、每一次合并都亲手画过一次之后很多选择题的答案会直接蹦出来根本不需要背。最后分享一个我自己的土办法考前找一张白纸把七个排序按我上面分组的方式各手写一遍全过程不看书、不查代码。我第一次完整手写完快排和堆排序之后很多自以为了解、实际上模糊不清的细节全部浮出了水面。排序这部分知识就是这样代码亲手写出来的价值永远大于眼睛瞟过去的回忆感。数据结构里的排序值得你多花几次手动模拟的时间。
返回列表