golang container/heap使用

内容分享2周前发布
0 1 0

container/heap 是 Go 标准库中用于实现 堆(Heap)优先队列(Priority Queue) 的包。它不直接提供一个“堆类型”,而是提供一组操作函数(如 heap.Init, heap.Push, heap.Pop 等),需要自定义一个底层容器(一般是切片)并实现 heap.Interface 接口

✅ 一、核心原理

Go 的堆是 最小堆(min-heap),即堆顶(h[0])是最小元素。但可以通过自定义 Less 方法实现 最大堆 或其他排序逻辑。

必须实现的接口:heap.Interface

type Interface interface {
sort.Interface // 包含 Len(), Less(i, j), Swap(i, j)
Push(x interface{}) // 向堆中添加元素(注意:参数是 interface{})
Pop() interface{} // 弹出并返回堆顶元素(返回 interface{})
}

⚠️ 注意:Push 和 Pop 是 给 heap 包内部调用的,应使用 heap.Push(h, x) 和 heap.Pop(h) 来操作。

✅ 二、基本使用步骤

  • 定义一个类型(一般基于 []T)。
  • 为该类型实现 heap.Interface 的 5 个方法。
  • 使用 heap.Init 初始化。
  • 使用 heap.Push / heap.Pop 操作堆。

✅ 三、完整示例

示例 1:最小整数堆(Min-Heap)

package main

import (
    "container/heap"
    "fmt"
)

// 定义堆类型(基于 []int)
type IntHeap []int

// 实现 sort.Interface
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] }

// 实现 heap.Interface 的 Push 和 Pop
func (h *IntHeap) Push(x any) {
    *h = append(*h, x.(int))
}

func (h *IntHeap) Pop() any {
    old := *h
    n := len(old)
    x := old[n-1]     // 取最后一个元素
    *h = old[0 : n-1] // 缩短切片
    return x
}

func main() {
    h := &IntHeap{4, 2, 5, 1, 3}

    // 初始化堆(建立堆结构)
    heap.Init(h)
    fmt.Println("初始堆:", *h) // 堆化后不必定有序,但 h[0] 是最小值

    // 入堆
    heap.Push(h, 0)
    fmt.Println("Push(0) 后:", *h)

    // 出堆(弹出最小值)
    min := heap.Pop(h).(int)
    fmt.Println("Pop() =>", min) // 0
    fmt.Println("剩余堆:", *h)
}

示例 2:最大堆(Max-Heap)

只需修改 Less 方法:

func (h IntHeap) Less(i, j int) bool {
return h[i] > h[j] // 最大堆:父节点 > 子节点
}

这样 heap.Pop() 就会弹出最大值

示例 3:自定义结构体(如任务优先级)

package main

import (
    "container/heap"
    "fmt"
)

type Task struct {
    Name     string
    Priority int // 数字越小,优先级越高(可按需调整)
}

type TaskHeap []Task

func (h TaskHeap) Len() int      { return len(h) }
func (h TaskHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h TaskHeap) Less(i, j int) bool {
    return h[i].Priority < h[j].Priority // 最小堆:优先级数字小的在堆顶
}

func (h *TaskHeap) Push(x any) {
    *h = append(*h, x.(Task))
}

func (h *TaskHeap) Pop() any {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

func main() {
    // 使用
    tasks := &TaskHeap{
       {"紧急修复", 1},
       {"日常会议", 3},
       {"代码审查", 2},
    }
    heap.Init(tasks)
    top := heap.Pop(tasks).(Task)
    fmt.Println("最高优先级任务:", top.Name) // 紧急修复
}

✅ 四、常用操作总结

操作

函数

说明

初始化堆

heap.Init(h)

将切片转换为堆结构(O(n))

入堆

heap.Push(h, x)

插入元素并维护堆性质(O(log n))

出堆

heap.Pop(h)

弹出堆顶元素(O(log n))

调整堆

heap.Fix(h, i)

当 h[i]

值改变后,修复堆(O(log n))

移除元素

heap.Remove(h, i)

移除索引 i

处的元素(O(log n))

Fix 和 Remove 很少用,但对动态更新优先级很有用(如 Dijkstra 算法)。

⚠️ 五、注意事项

  • 不要直接修改堆底层数组如果直接改 h[i] = newValue,堆结构会被破坏!正确做法:使用 heap.Fix(h, i) 修复。
  • Push/Pop 方法是给 heap 包用的应调用 heap.Push(h, x),而不是 h.Push(x)。
  • 类型安全问题由于使用 interface{},需要类型断言(Go 1.18+ 可封装泛型版本)。
  • 不是并发安全的多 goroutine 访问需加锁。

✅ 六、典型应用场景

  • 优先队列(任务调度、事件处理)
  • Top-K 问题(如“找出最大的 10 个数”)
  • 堆排序(虽然 Go 有 sort 包,但 heap 可流式处理)
  • 图算法(Dijkstra、Prim)
© 版权声明

相关文章

1 条评论

您必须登录才能参与评论!
立即登录
none
暂无评论...