堆,使用数组表示时,其形状与完全二叉树相似。堆只有大根堆与小根堆之分,大根堆即父节点元素比孩子节点元素大的堆,小根堆相反。
站在父节点角度:
父节点下标:i
左孩子下标:2 * i + 1
右孩子下标:2 * i + 2
站在子节点角度:
当前下标:i
父节点下标:(i - 1) / 2
站在父节点做堆调整的思路是,将父节点的值与左右子节点的值做比较,在左右两个子节点中选取一个较大的值,与父节点的值交换。再从较大的子节点出发,重复上述操作,直到抵达数组末尾,或父节点已经大于子节点。这是一种自顶向下的堆调整思路。
假设有数组:[3,1,7,8,9],我们将其调整为大根堆
3
/ \
1 7
/ \
8 9
1. 3,7 互换
【7】
/ \
1 【3】
/ \
8 9
2. 1,9互换
7
/ \
【9】 3
/ \
8 【1】
3. 7,9互换
【9】
/ \
【7】 3
/ \
8 1
4. 7,8互换
9
/ \
【8】 3
/ \
【7】 1
从第一个非叶子节点开始做堆的调整即可,不用从叶节点开始。从叶节点开始也不错。只是增加了常数时间复杂度
堆排序:将堆顶的元素与数组的末尾元素互换,数组末尾向前移动,从堆顶到数组末尾位置做堆调整。重复上述操作,直到数组末尾位置为0,数组自然就有序了。
func heapify(arr []int, index, size int) {
for max := 0; index < size; index = max {
max = 2*index + 1
if max+1 < size && arr[max] < arr[max+1] {
max++
}
if max < size && arr[index] < arr[max] {
arr[index], arr[max] = arr[max], arr[index]
} else {
return
}
}
}
func hepSort(arr []int) {
for i := len(arr) / 2 - 1 ; i >= 0; i-- { // 从第一个非叶节点出发
heapify(arr, i, len(arr)) // 做堆调整
}
for i := len(arr) - 1; i >= 0; i-- { // 从数组末尾出发
arr[i], arr[0] = arr[0], arr[i] // 将数组末尾与数组首元素互换,此时数组最后一个元素是最大或最小的
heapify(arr, 0, i) // 从数组头做堆调整,范围到数组末尾
}
}