堆与双堆模板
一句话说明
单堆通常用来维护 Top K 或当前极值;双堆则把数据分成较小一半和较大一半,最典型应用就是中位数。
Go 里堆的写法先统一
Go 通常直接基于 container/heap 实现。
最小堆默认最自然,最大堆一般通过改 Less 或取反来做。
Go 模板:最小堆
type IntMinHeap []int
func (h IntMinHeap) Len() int { return len(h) }
func (h IntMinHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntMinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntMinHeap) Push(x any) {
*h = append(*h, x.(int))
}
func (h *IntMinHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}Go 模板:Top K 最大值
思路是维护一个大小为 k 的最小堆:
- 堆里永远保存当前最大的
k个数 - 堆顶就是这
k个数里的最小值,也就是淘汰线
func TopKLargest(nums []int, k int) []int {
if k <= 0 {
return nil
}
h := &IntMinHeap{}
heap.Init(h)
for _, v := range nums {
if h.Len() < k {
heap.Push(h, v)
continue
}
if v > (*h)[0] {
heap.Pop(h)
heap.Push(h, v)
}
}
result := append([]int(nil), (*h)...)
sort.Sort(sort.Reverse(sort.IntSlice(result)))
return result
}双堆到底在维护什么
双堆的核心不是“两个堆而已”,而是维护这两个不变量:
small存较小的一半large存较大的一半
同时保证:
small里的所有值都不大于largesmall的元素个数等于large,或者只多一个
这样中位数就会落在堆顶。
Go 模板:数据流中位数
type IntMaxHeap []int
func (h IntMaxHeap) Len() int { return len(h) }
func (h IntMaxHeap) Less(i, j int) bool { return h[i] > h[j] }
func (h IntMaxHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntMaxHeap) Push(x any) {
*h = append(*h, x.(int))
}
func (h *IntMaxHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
type MedianFinder struct {
small *IntMaxHeap
large *IntMinHeap
}
func NewMedianFinder() *MedianFinder {
small := &IntMaxHeap{}
large := &IntMinHeap{}
heap.Init(small)
heap.Init(large)
return &MedianFinder{small: small, large: large}
}
func (mf *MedianFinder) AddNum(num int) {
heap.Push(mf.small, num)
heap.Push(mf.large, heap.Pop(mf.small))
if mf.large.Len() > mf.small.Len() {
heap.Push(mf.small, heap.Pop(mf.large))
}
}
func (mf *MedianFinder) FindMedian() float64 {
if mf.small.Len() > mf.large.Len() {
return float64((*mf.small)[0])
}
return float64((*mf.small)[0]+(*mf.large)[0]) / 2.0
}什么时候该想到双堆
- 数据流中位数
- 动态维护第
k大 / 第k小附近的边界 - 一边维护左半最大值,一边维护右半最小值
易错点
堆与双堆模板最容易错的地方
- Go 的
Pop/Push是给heap包回调用的,不是你自己直接随便改切片。- 双堆平衡时,先保证顺序关系,再保证数量关系。
- 最大堆和最小堆不要把
Less写反。
相关主题
返回:算法模板