AI时代的算法思维:10大经典排序(第二篇)
文章摘要 本文介绍了三种经典排序算法:归并排序(稳定分治,适合外部排序和链表排序)、堆排序(基于堆结构,适合Top-K问题)和计数排序(空间换时间,适合小范围整数排序)。归并排序稳定且时间复杂度稳定为O(n log n),但需额外空间;堆排序原地排序且复杂度稳定,但缓存命中率低;计数排序线性时间复杂度,但仅适用于特定场景。每种算法都配有流程图、伪代码、应用场景和复杂度分析,帮助理解其原理与适用性。
接上一篇《AI时代的算法思维:10大经典排序(第一篇)》
地址:https://blog.csdn.net/jiarry/article/details/159511929
7. 堆排序(Heap Sort)— 树形选择
算法原理:利用堆(完全二叉树)这种数据结构来排序。先把数组构建成一个大顶堆(每个父节点都大于等于子节点),然后不断取出堆顶(最大值)交换到数组末尾,并对剩余元素重新堆化处理,直到全部有序。
生活类比:就像整理一堆水果,把最大的放在顶上,每次取出最顶上的水果放到盘子里,然后让剩下的水果重新“自动堆成一座山”,下一次再取最大的。不断重复,最终就把所有水果从大到小排好。
流程图
伪代码
function HeapSort(arr):
n = length(arr)
// 建堆:从最后一个非叶节点开始,自底向上堆化
for i = n/2 - 1 downto 0:
SiftDown(arr, i, n)
// 排序:反复取堆顶放到末尾
for i = n - 1 downto 1:
swap(arr[0], arr[i]) // 堆顶(最大值)放到末尾
SiftDown(arr, 0, i) // 对剩余元素重新堆化
function SiftDown(arr, parent, size):
while 2 * parent + 1 < size: // 还有子节点
child = 2 * parent + 1 // 左子节点
if child + 1 < size and arr[child + 1] > arr[child]:
child = child + 1 // 选较大的子节点
if arr[parent] >= arr[child]:
break // 父节点已经最大,停止
swap(arr[parent], arr[child]) // 下沉
parent = child
应用场景
- 优先队列调度:操作系统进程调度、网络包调度底层均基于堆结构
- 大规模 Top‑K 问题:从10亿级数据中取前K个值,只需维护大小为K的小顶堆,复杂度 O (n log k)
- 内存受限环境:原地排序 O (1) 空间,最坏复杂度稳定 O (n log n)
- 高性能定时器:Nginx、Go Runtime、Redis 均使用堆管理定时器
复杂度
| 最好 | 平均 | 最坏 | 空间 | 稳定性 |
|---|---|---|---|---|
| O(n log n) | O(n log n) | O(n log n) | O(1) | 不稳定 |
堆排序理论上非常理想——原地排序且时间复杂度稳定为 O(n log n),但在实际应用中通常比快速排序慢。原因在于堆化操作需要在数组中频繁跳跃访问父子节点,父节点和子节点在内存中相距较远,CPU 缓存命中率低,导致效率下降。
8. 计数排序(Counting Sort)— 用空间换时间
算法原理:统计每个元素出现的次数,用额外数组记录到对应下标,再按顺序输出,实现排序,不进行元素比较。属于非比较排序,适合元素范围不大、为整数的数组,时间复杂度 O(n + k)。
生活类比: 就像统计考试分数:准备一个 0–100 的计数表,遍历所有试卷,把每个分数出现的次数加 1。最后从 0 分到 100 分依次输出,每个分数出现几次就写几次,这样就得到排序好的成绩单。
流程图
伪代码
function CountingSort(arr):
max_val = max(arr) // 找到最大值
count = array of (max_val + 1) zeros // 创建计数数组
for x in arr: // 统计每个值的出现次数
count[x] = count[x] + 1
for i = 1 to max_val: // 前缀和:count[i]变为"<=i的元素总数"
count[i] = count[i] + count[i - 1]
output = array of length(arr)
for i = length(arr) - 1 downto 0: // 反向遍历保证稳定性
output[count[arr[i]] - 1] = arr[i]
count[arr[i]] = count[arr[i]] - 1
return output
应用场景
- 年龄排序:范围 0–150 固定且集中,计数排序可实现极致线性效率
- 考试成绩排序:分数 0–100 范围有限,无需比较即可完成排序与统计
- 字符频率统计:ASCII 字符集 0–127,非常适合计数排序
- 基数排序的子过程:基数排序每一位的稳定排序通常由计数排序实现
复杂度
| 最好 | 平均 | 最坏 | 空间 | 稳定性 |
|---|---|---|---|---|
| O(n + k) | O(n + k) | O(n + k) | O(n + k) | 稳定 |
计数排序高效、直接,不依赖比较,尤其适合数值范围有限的数据,就像统计考试分数一样,是一种巧妙且实用的排序方法。
9. 基数排序(Radix Sort)— 逐位排序
算法原理:不直接比较数字大小,而是按个位、十位、百位……逐位处理。每一位使用稳定排序(通常是计数排序),保证低位顺序在高位处理时不被破坏,最终得到完整有序的序列。属于非比较排序,适合整数且位数有限的数组。
生活类比:就像整理邮政编码的信件,先按最后一位数字分堆,再按倒数第二位分堆……逐位处理,直到按完整邮编顺序排列好所有信件。
流程图
伪代码
function RadixSort(arr):
max_val = max(arr)
d = number_of_digits(max_val) // 最大位数
exp = 1 // 当前位的权重:1, 10, 100, ...
for digit = 1 to d:
CountingSortByDigit(arr, exp) // 按当前位做稳定排序
exp = exp * 10
function CountingSortByDigit(arr, exp):
count = array of 10 zeros // 0-9 十个桶
output = array of length(arr)
for x in arr:
digit = (x / exp) % 10 // 取出当前位
count[digit]++
for i = 1 to 9:
count[i] += count[i - 1] // 前缀和
for i = length(arr) - 1 downto 0: // 反向遍历保证稳定性
digit = (arr[i] / exp) % 10
output[count[digit] - 1] = arr[i]
count[digit]--
copy output to arr
应用场景
- 手机号排序:11 位定长数字,效率 O (11n),远快于 O (n log n)
- IP 地址排序:4 段固定格式数字,天然适合逐段排序
- 身份证号排序:定长数字编码,规则统一,可实现线性时间排序
- 大规模整数 / 定长字符串:位数固定时,O (dn) 接近线性,海量数据下优势巨大
复杂度
| 最好 | 平均 | 最坏 | 空间 | 稳定性 |
|---|---|---|---|---|
| O(n × d) | O(n × d) | O(n × d) | O(n + k) | 稳定 |
基数排序不依赖比较,高效处理大规模整数或固定长度字符串,就像邮局分拣信件一样,是实践中验证过的排序智慧。
10. 桶排序(Bucket Sort)— 分桶映射
算法原理:将数据按数值区间划分成若干桶,每个桶内部单独排序(通常用插入排序),然后按桶的顺序依次合并。效率依赖于数据均匀分布,每个桶元素较少时,总体性能最佳。属于非比较排序,适合分布较均匀的数值数组。
生活类比:就像收集水果,把苹果按大小或颜色放到不同的篮子里,每个篮子里再整理一下,最后按篮子顺序排列所有苹果,就得到整齐的果堆。
流程图
伪代码
function BucketSort(arr):
n = length(arr)
min_val = min(arr)
max_val = max(arr)
bucket_count = n // 桶数量通常取n
bucket_size = (max_val - min_val + 1) / bucket_count
buckets = array of bucket_count empty lists
for x in arr: // 将元素分配到桶中
idx = (x - min_val) / bucket_size
buckets[idx].append(x)
for each bucket in buckets: // 桶内排序(通常用插入排序)
InsertionSort(bucket)
result = concatenate all buckets // 按桶顺序拼接
return result
应用场景
- 均匀分布浮点数排序:如 0~1 随机浮点数,分桶后接近线性时间
- 分段统计排序:成绩分段、商品价格区间、用户年龄段归类等业务场景
- 图像颜色直方图:按像素值分桶统计,是图像处理经典应用
- 请求负载均衡:按特征值分桶分发流量,实现均匀调度与分流
复杂度
| 最好 | 平均 | 最坏 | 空间 | 稳定性 |
|---|---|---|---|---|
| O(n + k) | O(n + k) | O(n²) | O(n + k) | 稳定 |
桶排序适合均匀分布的数据,高效且直观,就像按篮子整理水果一样,是实践中处理特定场景的稳定排序方法。
四、10大排序算法特点回顾
| 算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 | 说明 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 | 相邻元素两两比较,最大或最小元素逐轮“冒”到最后 |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 | 每轮选择未排序里最小(或最大)元素放到已排序末尾 |
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 | 类似打扑克,从未排序中取元素插入到已排序序列中 |
| 快速排序 | O(n log n) | O(n²) | O(log n) | 不稳定 | 分治+分区,选基准元素将数组拆分,递归排序左右区间 |
| 归并排序 | O(n log n) | O(n log n) | O(n) | 稳定 | 递归拆分数组到单个元素,再不断向上合并两个有序子数组 |
| 堆排序 | O(n log n) | O(n log n) | O(1) | 不稳定 | 利用大顶堆选择最大元素放到末尾,原地排序 |
| 希尔排序 | O(n^1.3) | O(n²) | O(1) | 不稳定 | 分组式插入排序,先大步长分组排序再逐步缩小步长 |
| 计数排序 | O(n + k) | O(n + k) | O(n + k) | 稳定 | 不比较元素大小,统计每个值出现的次数并直接输出 |
| 基数排序 | O(n × d) | O(n × d) | O(n + k) | 稳定 | 按位从低到高分别排序,最后从高到低合并数据 |
| 桶排序 | O(n + k) | O(n²) | O(n + k) | 稳定 | 将数据分成若干桶,桶内排序后再将全部桶合并 |
10大排序算法的多语言实现(C/Java/Go/Python/JS/TS/Rust)源码地址:https://github.com/microwind/algorithms
五、AI时代,如何指导AI选择排序算法?
前面回顾了10大排序算法的特点与适用场景,现在回到文章开头的问题:
AI可以轻松生成算法代码,那作为程序员,我们还有必要学习算法吗?
AI时代,我们无需手写代码了,但需要理解算法背后的思想。只有这样才能根据实际场景,指导AI做出合适的选择。
下面看下AI引起的编程变革。
1. 编程方式发生了本质变化
过去: 程序员编写排序代码
现在: 人工定义算法策略(需求拆解)和约束条件(性能/稳定性/成本), 指导AI实现
未来: 人只告诉AI需求目标,AI自主确定策略,自主执行合适方案
2. AI编程的发展阶段
3. 先分析需求和选择策略,再让AI实现代码
- 先明确约束:根据数据规模、稳定性要求、内存限制、键值范围与分布特征,确定算法边界
- 通用场景首选:普通数组优先使用快速排序;链表或需要稳定排序时选择归并排序
- 小规模/近乎有序数据:插入排序效果最好;也可用“无交换提前结束”的冒泡作为简单有序性检测
- 整数且范围有限:计数排序、基数排序、桶排序可将时间复杂度降至线性级别,性能优势极大
- 最坏情况防护:快排应使用随机或三数取中选基准,小分区切换插入排序;大量重复元素时使用三路划分
AI时代,实现排序算法本身已经不重要了,重要的是选择哪种算法、为什么这么选。理解这些算法思想,指导AI时我们才会从容不迫。
相关链接
更多推荐

所有评论(0)