满足这两点,它就是一个堆:
- 堆是一个完全二叉树;
- 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值。
从图中我们可以看到,数组中下标为 i 的节点的左子节点,就是下标为 i∗2 的节点,右子节点就是下标为 i∗2+1 的节点,父节点就是下标为 2i 的节点。
- 往堆中插入一个元素
往堆中插入一个元素后,我们需要继续满足堆的两个特性。就需要进行调整,让其重新满足堆的特性,这个过程我们起了一个名字,就叫作堆化(heapify)。
堆化实际上有两种,从下往上和从上往下。这里我先讲从下往上的堆化方法。堆化非常简单,就是顺着节点所在的路径,向上或者向下,对比,然后交换。
从下往上堆化

- 删除堆顶元素
删除堆顶元素之后,把最后一个节点放到堆顶,然后利用同样的父子节点对比方法。对于不满足父子节点大小关系的,互换两个节点,并且重复进行这个过程,直到父子节点之间满足大小关系为止。
从上往下堆化
堆化的时间复杂度跟树的高度成正比,也就是 O(logn)。
我们可以把堆排序的过程大致分解成两个大的步骤,建堆和排序。
- 建堆
第一种是在堆中插入一个元素的思路。尽管数组中包含 n 个数据,但是我们可以假设,起初堆中只包含一个数据,就是下标为 1 的数据。然后,我们调用前面讲的插入操作,将下标从 2 到 n 的数据依次插入到堆中。这样我们就将包含 n 个数据的数组,组织成了堆。
第二种实现思路是从后往前处理数组,并且每个数据都是从上往下堆化。因为叶子节点往下堆化只能自己跟自己比较,所以我们直接从第一个非叶子节点开始,依次堆化就行了。

建堆过程的时间复杂度是 O(n)。
- 排序
建堆结束之后,把堆顶跟最后一个元素交换,然后再通过堆化的方法,将剩下的 n−1 个元素重新构建成堆。堆化完成之后,我们再取堆顶的元素,放到下标是 n−1 的位置,一直重复这个过程,直到最后堆中只剩下标为 1 的一个元素,排序工作就完成了。
堆排序包括建堆和排序两个操作,建堆过程的时间复杂度是 O(n),排序过程的时间复杂度是 O(nlogn),所以,堆排序整体的时间复杂度是 O(nlogn)。
堆排序不是稳定的排序算法,因为在排序的过程,存在将堆的最后一个节点跟堆顶节点互换的操作,所以就有可能改变值相同数据的原始相对顺序。
堆排序的应用:
- 优先级队列
- 合并有序小文件
- 高性能定时器
- 利用堆求 Top K
- 利用堆求中位数
heap.go
package pqueue
func adjustHeap(src []Node, start, end int) {
if start >= end {
return
}
// 只需要保证优先级最高的节点在 src[1] 的位置即可
for i := end / 2; i >= start; i-- {
high := i
if src[high].priority < src[2*i].priority {
high = 2 * i
}
if 2*i+1 <= end && src[high].priority < src[2*i+1].priority {
high = 2*i + 1
}
if high == i {
continue
}
src[high], src[i] = src[i], src[high]
}
}
priority_queue.go
package pqueue
// Node 队列节点
type Node struct {
value int
priority int
}
// PQueue priority queue
type PQueue struct {
heap []Node
capacity int
used int
}
// NewPriorityQueue new
func NewPriorityQueue(capacity int) PQueue {
return PQueue{
heap: make([]Node, capacity+1, capacity+1),
capacity: capacity,
used: 0,
}
}
// Push 入队
func (q *PQueue) Push(node Node) {
if q.used > q.capacity {
// 队列已满
return
}
q.used++
q.heap[q.used] = node
// 堆化可以放在 Pop 中
// adjustHeap(q.heap, 1, q.used)
}
// Pop 出队列
func (q *PQueue) Pop() Node {
if q.used == 0 {
return Node{-1, -1}
}
// 先堆化, 再取堆顶元素
adjustHeap(q.heap, 1, q.used)
node := q.heap[1]
q.heap[1] = q.heap[q.used]
q.used--
return node
}
// Top 获取队列顶部元素
func (q *PQueue) Top() Node {
if q.used == 0 {
return Node{-1, -1}
}
adjustHeap(q.heap, 1, q.used)
return q.heap[1]
}
以上内容摘自《数据结构与算法之美》课程,来学习更多精彩内容吧。


14万+

被折叠的 条评论
为什么被折叠?



