图文堆排序
曦子图文理解堆排序.
缘起
堆排序咋一看有点反直觉, 这里详细记录下算法过程。
了解几个概念
- 完全二叉树
- 大堆/小堆
大堆就是父结点大于等于其子结点的值.
思路
考虑有个数组 [2, 1, 3] 我们想降序排列也就是变成 [3, 2, 1]。
我们先用完全二叉树的形式他构建成大堆, 如下:
很简单, 用代码也能简单的实现, 下面介绍下一个概念 heapify
, 简单来说就是让三个结点最大的在父节点.
如何排序呢, 我们把最后的叶子结点 2 和root
结点 3 交换, 然后最大值就在最后的叶子结点, 把最后一个节点摘掉, 那么取到了当前最大值, 但是当前的堆可能是乱掉的, 那么我们再次执行 heapify
, 然后移除最后的叶子结点, 如此反复即可.
细节
了解了大致原理, 我们再来深入到细节。
heapify 函数
假设我们有一个数组[6, 7, 3, 2, 1, 9, 8, 5]
, 构造成二叉树.
从图上我们可以看出, 当我们拿到数组的 index 可以轻松计算出其父结点
和子结点
.
heapify
函数也很简单, 如下:
func heapify(nums []int, n int, i int) {
:= i
largest := 2*i + 1
l := 2*i + 2
r if l < n && nums[l] > nums[largest] {
= l
largest }
if r < n && nums[r] > nums[largest] {
= r
largest }
if largest != i {
[i], nums[largest] = nums[largest], nums[i]
nums// 对子结点进行调整
(nums, n, largest)
heapify}
}
构建大堆
在一个简单的二叉树(只有三个结点的时候)构建成大堆我们已经处理过了, 怎么让一个复杂的二叉树构建成大堆呢?
逻辑上很简单, 我们只要从最后一个叶子结点的父结点
往根结点方向一直执行 heapify
即可.
最后一个叶子结点的下标是 n-1
, 那么根据上述公式其父节点为 即 - 1.
func heapSort(nums []int) {
:= len(nums)
n for i := n/2 - 1; i >= 0; i-- {
(nums, n, i)
heapify}
}
执行后数组从 [6, 7, 3, 2, 1, 9, 8, 5]
变为 [9 7 8 5 1 3 6 2]
.
final
上面我们成功构建了大堆, 那么现在我们只有每构建成大堆, 把root
结点跟最后的叶子结点交换, 移除叶子结点(取出最大值), 然后再对剩下的元素进行heapify
, 继续取最大值, 反复操作即可.
func heapSort(nums []int) {
:= len(nums)
n for i := n/2 - 1; i >= 0; i-- {
(nums, n, i)
heapify}
for i := n - 1; i >= 0; i-- {
[0], nums[i] = nums[i], nums[0]
nums(nums, i, 0)
heapify}
}
总结
堆排序很容易忘记, 详细记一下.