接上一篇《AI时代的算法思维:10大经典排序(第一篇)》

地址:https://blog.csdn.net/jiarry/article/details/159511929

7. 堆排序(Heap Sort)— 树形选择

算法原理:利用堆(完全二叉树)这种数据结构来排序。先把数组构建成一个大顶堆(每个父节点都大于等于子节点),然后不断取出堆顶(最大值)交换到数组末尾,并对剩余元素重新堆化处理,直到全部有序。

生活类比:就像整理一堆水果,把最大的放在顶上,每次取出最顶上的水果放到盘子里,然后让剩下的水果重新“自动堆成一座山”,下一次再取最大的。不断重复,最终就把所有水果从大到小排好。

流程图

开始

构建大顶堆
从最后一个非叶节点
到根逐个下沉

未排序部分
长度 > 1 ?

排序完成

交换堆顶与
未排序部分末尾

未排序范围 - 1

对堆顶执行下沉
sift down

伪代码
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 分依次输出,每个分数出现几次就写几次,这样就得到排序好的成绩单。

流程图

开始

找到最大值 max
创建计数数组 count[0..max]

遍历数组
统计每个元素出现次数

对 count 做前缀和
确定每个元素的位置

反向遍历原数组
按 count 放入输出数组

排序完成

伪代码
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)— 逐位排序

算法原理:不直接比较数字大小,而是按个位、十位、百位……逐位处理。每一位使用稳定排序(通常是计数排序),保证低位顺序在高位处理时不被破坏,最终得到完整有序的序列。属于非比较排序,适合整数且位数有限的数组。

生活类比:就像整理邮政编码的信件,先按最后一位数字分堆,再按倒数第二位分堆……逐位处理,直到按完整邮编顺序排列好所有信件。

流程图

处理下一位

开始

计算数组
最大位数 d

digit = 1
从最低位开始

digit ≤ d ?
(最低位 → 最高位)

排序完成

对数组 a[0..n-1]
按 digit 位进行稳定排序
(计数排序)

digit++

伪代码
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)— 分桶映射

算法原理:将数据按数值区间划分成若干桶,每个桶内部单独排序(通常用插入排序),然后按桶的顺序依次合并。效率依赖于数据均匀分布,每个桶元素较少时,总体性能最佳。属于非比较排序,适合分布较均匀的数值数组。

生活类比:就像收集水果,把苹果按大小或颜色放到不同的篮子里,每个篮子里再整理一下,最后按篮子顺序排列所有苹果,就得到整齐的果堆。

流程图

是,处理下一桶

开始

创建 k 个空桶
确定值域范围

遍历数组
将每个元素
分配到对应桶

对每个非空桶
内部排序

按桶顺序
依次拼接所有元素

还有未处理的桶 ?

排序完成

伪代码
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实现
未来: 人只告诉AI需求目标,AI自主确定策略,自主执行合适方案

2. AI编程的发展阶段

2023以前
传统模式

2023-2024
Copilot模式

2025+
Agent模式

2026+
Agentic模式

手写代码
实现功能
角色:执行者

L1 AI Copilot
辅助编码
角色:增强执行

L2 AI Agent
指导AI
角色:指挥者

L3 Agentic AI
驱动AI
角色:决策者

3. 先分析需求和选择策略,再让AI实现代码

  • 先明确约束:根据数据规模、稳定性要求、内存限制、键值范围与分布特征,确定算法边界
  • 通用场景首选:普通数组优先使用快速排序;链表或需要稳定排序时选择归并排序
  • 小规模/近乎有序数据:插入排序效果最好;也可用“无交换提前结束”的冒泡作为简单有序性检测
  • 整数且范围有限:计数排序、基数排序、桶排序可将时间复杂度降至线性级别,性能优势极大
  • 最坏情况防护:快排应使用随机或三数取中选基准,小分区切换插入排序;大量重复元素时使用三路划分

AI时代,实现排序算法本身已经不重要了,重要的是选择哪种算法、为什么这么选。理解这些算法思想,指导AI时我们才会从容不迫。


相关链接

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐