# 堆
# 概念与特征
堆是将一组数据按照完全二叉树的存储顺序,将数据存储在一个一维数组中的结构。堆有两种结构,一种称为大顶堆,一种称为小顶堆
小顶堆:任意节点的值均小于等于它的左右孩子,并且最小的值位于堆顶,即根节点处。
大顶堆:任意节点的值均大于等于它的左右孩子,并且最大的值位于堆顶,即根节点处。
有些地方也叫大根堆、小根堆,或者最大堆、最小堆都一个意思。
优先队列:说到底还是一种队列,他的工作就是 poll ()/peek () 出队列中最大 / 最小的那个元素,所以叫带有优先级的队列。能够实现优先功能的策略不一定只有堆,例如二项堆、平衡树、线段树、C++ 里会用二进制分组的 vector 来实现一个优先队列。
堆:堆是一个很大的概念 他并不一定是完全二叉树。我们之前用完全二叉树是因为这个很容易被数组储存,但是除了这种二叉堆之外,我们还有二项堆、斐波那契堆、这种堆就不属于二叉树。
golang 堆可以借助 container/heap
去实现
container 源码解析:https://blog.twelveeee.top/2023/Go/Lib/sort_and_container/#container
import "container/heap" | |
type IntHeap []int | |
func (h *IntHeap) Push(x interface{}) { | |
*h = append(*h, x.(int)) | |
} | |
func (h *IntHeap) Pop() interface{} { | |
x := (*h)[len(*h)-1] | |
*h = (*h)[:len(*h)-1] | |
return x | |
} | |
func (h *IntHeap) Len() int { | |
return len(*h) | |
} | |
func (h *IntHeap) Less(i, j int) bool { | |
return (*h)[i] < (*h)[j] | |
} | |
func (h *IntHeap) Swap(i, j int) { | |
(*h)[i], (*h)[j] = (*h)[j], (*h)[i] | |
} | |
func newIntHeap() *IntHeap { | |
h := &IntHeap{} | |
heap.Init(h) | |
return h | |
} |
func TestGoHeap(t *testing.T) { | |
h := newIntHeap() | |
heap.Push(h, 5) | |
heap.Push(h, 4) | |
heap.Push(h, 1) | |
heap.Push(h, 3) | |
heap.Push(h, 2) | |
if h.Len() != 5 { | |
t.Error("Heap.Len() != 5") | |
} | |
for h.Len() > 0 { | |
t.Log(heap.Pop(h)) | |
} | |
} |
# 普通算法题
# 1. 合并 K 个升序链表
https://leetcode.cn/problems/merge-k-sorted-lists/description/
https://github.com/Twelveeee/AlgorithmCamp2023/blob/main/14_Heap/level_2/topic_1.go
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
func mergeKLists(lists []*ListNode) *ListNode { | |
h := &ListNodeHeap{} | |
heap.Init(h) | |
for _, node := range lists { | |
if node != nil { | |
heap.Push(h, node) | |
} | |
} | |
dummyHead := &ListNode{} | |
curr := dummyHead | |
for len(*h) > 0 { | |
node := heap.Pop(h).(*ListNode) | |
if node.Next != nil { | |
heap.Push(h, node.Next) | |
} | |
curr.Next = node | |
curr = curr.Next | |
} | |
return dummyHead.Next | |
} | |
type ListNodeHeap []*ListNode | |
func (h *ListNodeHeap) Len() int { return len(*h) } | |
func (h *ListNodeHeap) Less(i, j int) bool { return (*h)[i].Val < (*h)[j].Val } | |
func (h *ListNodeHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } | |
func (h *ListNodeHeap) Push(val any) { *h = append(*h, val.(*ListNode)) } | |
func (h *ListNodeHeap) Pop() any { x := (*h)[len(*h)-1]; (*h) = (*h)[:len(*h)-1]; return x } |
# 中位数问题
两个堆,一个大顶堆,一个小顶堆。
# 2. 数据流的中位数
https://leetcode.cn/problems/find-median-from-data-stream/description/
https://github.com/Twelveeee/AlgorithmCamp2023/blob/main/14_Heap/level_3/topic_1.go
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
- 例如
arr = [2,3,4]
的中位数是3
。- 例如
arr = [2,3]
的中位数是(2 + 3) / 2 = 2.5
。实现 MedianFinder 类:
MedianFinder()
初始化MedianFinder
对象。void addNum(int num)
将数据流中的整数num
添加到数据结构中。double findMedian()
返回到目前为止所有元素的中位数。与实际答案相差10^-5
以内的答案将被接受。
type MedianFinder struct { | |
maxHeap *MaxHeap | |
minHeap *MinHeap | |
} | |
func Constructor() MedianFinder { | |
maxHeap, minHeap := &MaxHeap{}, &MinHeap{} | |
heap.Init(maxHeap) | |
heap.Init(minHeap) | |
return MedianFinder{maxHeap, minHeap} | |
} | |
func (this *MedianFinder) AddNum(num int) { | |
heap.Push(this.minHeap, num) | |
heap.Push(this.maxHeap, heap.Pop(this.minHeap)) | |
for this.minHeap.Len() < this.maxHeap.Len() { | |
heap.Push(this.minHeap, heap.Pop(this.maxHeap)) | |
} | |
} | |
func (this *MedianFinder) FindMedian() float64 { | |
if this.minHeap.Len() > this.maxHeap.Len() { | |
return float64(this.minHeap.Top()) | |
} | |
return float64(this.minHeap.Top()+this.maxHeap.Top()) / 2 | |
} | |
/** | |
* Your MedianFinder object will be instantiated and called as such: | |
* obj := Constructor(); | |
* obj.AddNum(num); | |
* param_2 := obj.FindMedian(); | |
*/ | |
type MinHeap []int | |
func (h *MinHeap) Len() int { return len(*h) } | |
func (h *MinHeap) Less(i, j int) bool { return (*h)[i] < (*h)[j] } | |
func (h *MinHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } | |
func (h *MinHeap) Push(val any) { (*h) = append((*h), val.(int)) } | |
func (h *MinHeap) Pop() any { | |
val := (*h)[len(*h)-1] | |
(*h) = (*h)[:len(*h)-1] | |
return val | |
} | |
func (h *MinHeap) Top() int { return (*h)[0] } | |
type MaxHeap []int | |
func (h *MaxHeap) Len() int { return len(*h) } | |
func (h *MaxHeap) Less(i, j int) bool { return (*h)[i] > (*h)[j] } | |
func (h *MaxHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } | |
func (h *MaxHeap) Push(val any) { (*h) = append((*h), val.(int)) } | |
func (h *MaxHeap) Pop() any { val := (*h)[len(*h)-1]; (*h) = (*h)[:len(*h)-1]; return val } | |
func (h *MaxHeap) Top() int { return (*h)[0] } |
# 第 k 大元素
第 k 大元素,使用小顶堆,保证栈顶元素是最小的元素。
如果堆的长度超过 k,则弹出栈顶元素,直到长度小于等于 k。
剩下的栈顶元素就是当前第 K 大的元素。
反之,第 k 小元素使用大顶堆。
# 3. 第 k 大元素
import ( | |
"container/heap" | |
"fmt" | |
) | |
type MinHeap []int | |
func (h MinHeap) Len() int { | |
return len(h) | |
} | |
func (h MinHeap) Less(i, j int) bool { | |
return h[i] < h[j] | |
} | |
func (h MinHeap) Swap(i, j int) { | |
h[i], h[j] = h[j], h[i] | |
} | |
func (h *MinHeap) Push(x interface{}) { | |
*h = append(*h, x.(int)) | |
} | |
func (h *MinHeap) Pop() interface{} { | |
old := *h | |
n := len(old) | |
x := old[n-1] | |
*h = old[0 : n-1] | |
return x | |
} | |
func findKthLargest(nums []int, k int) int { | |
if len(nums) < k { | |
return -1 | |
} | |
// 初始化最小堆 | |
h := &MinHeap{} | |
heap.Init(h) | |
// 维护堆的大小为 K | |
for _, num := range nums { | |
heap.Push(h, num) | |
if h.Len() > k { | |
heap.Pop(h) | |
} | |
} | |
// 堆顶元素即为第 K 大的元素 | |
return (*h)[0] | |
} | |
func main() { | |
nums := []int{3, 1, 4, 2, 5, 8, 7, 6} | |
k := 3 | |
kthLargest := findKthLargest(nums, k) | |
fmt.Printf("第%d大的元素为:%d\n", k, kthLargest) // 输出:第 3 大的元素为:6 | |
} |