别被“排序”这两个字骗了,觉得它只是教材里一个平平无奇的小节。在严蔚敏老师的《数据结构(C语言版)》里,排序章节是整个课程承上启下的核心枢纽——前承线性表、树、图这些物理结构,后启查找、文件检索等实际应用。很多考研人和自学党在这章折戟,不是代码看不明白,而是没把“排序的分类逻辑”和“每个算法要解决的关键痛点”串起来。
这篇东西不打算照着教材目录给你泛泛地念一遍,那没意思。就以整理排序方法为线索,把这十章里穿插着的插入类、交换类、选择类、归并和基数这几大阵营挨个拆开,讲讲每个算法的实现要点、适用场景、容易踩的坑,以及为什么《数据结构》要在这个节点把这几大排序一起端上来。理清这层逻辑,复习效率远比你盯着PPT硬背高得多。
1. 先建立全局观:严蔚敏教材里这几类排序是怎么分类的
很多刚翻到第十章的同学第一反应是:“怎么突然冒出这么多排序?”一口气从简单插入、冒泡、快速、选择、堆排一直看到归并和基数,头晕。这是因为排序算法本身就是一个极好的“算法设计策略展示台”——同一个“排好序”的需求,可以用不同的思想去实现,这也给了教材一个绝佳的机会,把前面第八章讲过的“分治”、“动态规划”之外的各种算法设计思路集中拉练一遍。
要理清思路,我的建议是先记住一个核心分类维度:按是否基于“关键字比较”来区分。
基于关键字比较的排序算法,本质上只做两件事:比较两个元素谁大谁小,然后决定是否交换位置。这一类里面,又可以根据“怎么比较”、“按什么顺序比较”分成三大类:
- 插入类:把新元素往已经有序的序列里插入。代表人物:直接插入排序、折半插入排序、希尔排序。
- 交换类:拿两个元素出来PK,逆序就交换。代表人物:冒泡排序、快速排序。
- 选择类:每一趟从待排序元素里挑出最小或最大值,放到确定的位置上。代表人物:简单选择排序、堆排序。
另有一类不靠比较,而是通过分配和收集来实现排序,这就是基数排序,它和前面几类完全不是一个维度的思路。在严蔚敏书里,这几类算法的讲解顺序其实暗合了“从简单直观到复杂高效”的认知曲线,也暗合了时空复杂度不断调剂、在特定场景下换最优解的设计逻辑。
掌握了分类框架后,再看某一种具体算法就问三个问题:“它的基本操作是什么?它为什么比同类的另一种算法更快?它适合跑在什么数据上?”这三个问题能打通,这一章就吃掉了一半。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 插入家族:从直接插入到希尔,一路跃迁的逻辑
2.1 直接插入排序是入门选手,但别小看它的局部有序优势
直接插入排序的思路非常朴素:就像打扑克牌时,你把新摸到的一张牌,从右往左跟手里的牌比较,找到合适位置插进去。落实到数组里就是:把待排序的记录逐个插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。
严蔚敏教材上给的代码用的是“哨兵”技巧,在数组下标0的位置留一个空位,每次插入前把待插入值复制到哨兵位置,这样在向前查找的时候就省掉了“下标是否越界”的判断。初学的时候不少人是懵的:为什么要浪费一个空间?但等你真的在链表插入和数组插入之间切换对比后,就会明白这是一个典型的“以空间换时间”思路,而且代码变得非常简洁,也规避了每趟循环都要判一次 j>=0 的开销。
复杂度上,最好情况是O(n),序列已经接近有序时移动次数极少。最坏和平均情况是O(n²)。因为一趟一趟地把后面的元素往前搬,所以它是稳定的。
这个算法单独拿出来意义一般,但它的变体思想极为重要:“对近乎有序的序列,插入排序就是牛”。后面很多排序,比如快排在小区间停手后“兜底”,实际上用的就是直接插入排序来挽救由递归产生的小块局部有序数据。
2.2 折半插入:数组的随机访问特性,正好被利用
直接插入排序最浪费时间的地方,在于查找插入位置的时候用的是顺序查找,那是一个一个挨个比。既然是数组,那查找“第一个大于当前值的位置”这种操作命中注定了可以用二分查找。
把比较次数从O(n)降为O(log₂n),这是折半插入排序干的活。但请注意——查找快了,元素的移动次数没有降!因为你找到位置后,仍然要把该位置之后的元素整体后移一位。所以折半插入排序的时间复杂度依然是O(n²),只不过常数因子要比直接插入排序小一些。
严蔚敏在教材里给出这个变体的用意,我理解是为了提醒读者:“比较”和“移动”是两个可以分开优化的维度,你优化查找,节省的是比较时间;你想省掉移动时间,得换一个存储结构,或者引入更多辅助空间。为了降低元素移动的开销,后面才会引出链表式的插入排序或希尔排序。
2.3 希尔排序:跨越式插入,让“长途搬运”提前完成
希尔排序是插入排序的升级版,它的核心洞察是:直接插入排序太“短视”了,每次都只跟紧挨着的相邻元素比,如果一个很小的元素在序列的最后一个位置,它要碍多少次眼、挪多少次窝才能到达最终位置?于是希尔想出办法:先让元素能跨过较大距离进行比较,让序列从“宏观”上先变成基本有序,再用插入排序做最后一步的“精修”。
具体操作是:取一个增量d,把所有下标相差d倍数的元素分成一组,组内做直接插入排序;接着缩小d,重复这个动作;直到d=1,整个序列做一次普通的直接插入排序。严蔚敏教材里下标从1开始存储,所以代码里 for (i = d + 1; i <= n; i++) 这种经典写法很多同学估计都背过,但必须注意理解:这行的本质就是“从第d+1个元素开始,让它和自己组里的前一个元素比较”。
希尔排序的复杂度非常难推导,教材上点到为止,只说明它和增量序列的选取有关。实践界用得较多的经验结论是:当增量序列按照“每次除以2向下
