1. 堆排序核心原理剖析
堆排序的精妙之处在于它巧妙地结合了完全二叉树和数组两种数据结构的优势。作为选择排序家族的一员,堆排序通过构建二叉堆这种特殊的数据结构,实现了高效的元素选择机制。
1.1 二叉堆的本质特性
二叉堆本质上是一棵完全二叉树,但它的存储方式却采用了数组这种紧凑结构。这种设计带来了几个关键优势:
- 数组存储避免了指针开销,内存利用率100%
- 通过简单的下标计算就能快速定位父子节点(父节点i的左子节点为2i+1,右子节点为2i+2)
- 完全二叉树的性质保证了树的高度始终维持在⌊log₂n⌋+1
最大堆的性质要求每个节点的值都必须大于或等于其子节点的值。这意味着堆顶元素始终是整个堆中的最大值。这个特性正是堆排序能够高效工作的基础。
1.2 堆排序的双阶段机制
堆排序算法可以清晰地划分为两个阶段:
-
堆构建阶段:将无序数组调整为一个合法的最大堆。这个过程从最后一个非叶子节点开始,采用自底向上的方式进行调整。选择这个起始点是因为叶子节点本身已经满足堆的性质(没有子节点),无需调整。
-
元素提取阶段:反复取出堆顶的最大值,将其与当前堆的末尾元素交换,然后缩小堆的范围并对新堆顶进行下沉调整。这个阶段持续到堆中只剩一个元素为止。
关键技巧:在构建堆时,从n/2-1开始倒序调整可以确保每个子树在调整时,其子树已经是合法堆,这种策略将建堆时间复杂度优化到了O(n)。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 堆排序的完整实现步骤
2.1 堆构建的详细过程
让我们用一个具体例子来演示堆构建的过程。假设初始数组为[3, 8, 2, 5, 1, 4]:
- 确定最后一个非叶子节点位置:len(arr)//2 - 1 = 2(值为2的节点)
- 调整节点2:
- 比较它和子节点(值4),无需交换
- 调整节点1(值8):
- 比较左右子节点(值5和1),左子节点更大
- 比较8和5,无需交换
- 调整节点0(值3):
- 比较左右子节点(值8和2),左子节点更大
- 比较3和8,需要交换 → 数组变为[8, 3, 2, 5, 1, 4]
- 对交换后的位置1(原值3)继续调整:
- 比较子节点(值5和1),左子节点更大
- 比较3和5,需要交换 → 数组变为[8, 5, 2, 3, 1, 4]
- 对新的位置3(原值3)无需进一步调整
最终得到的最大堆为[8, 5, 2, 3, 1, 4],其树形表示为:
code复制 8
/ \
5 2
/ \ /
3 1 4
2.2 排序阶段的逐步拆解
继续上面的例子,现在开始排序阶段:
- 第一次交换:堆顶8与末尾4交换 → [4, 5, 2, 3, 1, 8]
- 对新堆顶4进行下沉:
- 比较子节点5和2,左更大
- 比较4和5,交换 → [5, 4, 2, 3, 1, 8]
- 对新位置1(原值4):
- 比较子节点3
- 对新堆顶4进行下沉:
