first of all, the HEAP
就不介绍堆的基础知识啦
为了了解这个包,我们首先从堆这个概念入手。我刷面经的时候,也算是把堆的各种题目都已经做了遍,所以对于堆排序的算法也算是比较熟悉了,那么就从这里入手吧。
先手撕一个大顶堆实现的堆排序算法:
// 堆排序
func heapSort(arr []int) {
buildMaxHeap(arr, len(arr))
for i := len(arr) - 1; i > 0; i-- {
arr[i], arr[0] = arr[0], arr[i]
maxHeapify(arr, 0, i)
}
}
// 建堆
func buildMaxHeap(arr []int, length int) {
for i := length >> 1; i >= 0; i-- {
maxHeapify(arr, i, length)
}
}
// 调整大顶堆的某一个节点
func maxHeapify(arr []int, index, length int) {
l := left(index)
r := right(index)
max := index
if l < length && arr[l] > arr[max] {
max = l
}
if r < length && arr[r] > arr[max] {
max = r
}
if max != index {
arr[max], arr[index] = arr[index], arr[max]
maxHeapify(arr, max, length)
}
}
// 定位左子节点
func left(i int) int {
return 1 + i << 1
}
// 定位右子节点
func right(i int) int {
return 2 + i << 1
}
虽然和官方包提供的使用方式不同,但是也可留以与官方包中的内容进行对比
container.heap
1 包介绍
go的官方包的目标当然更加长远,是要为我们提供一个易于使用的容器。我们参考pkg.go.dev中对这个包的描述,就可以看到关于包的描述:
- 包heap为任何实现了
heap.Interface
的类型提供堆操作。堆是一棵树,其特性是每个节点都是其子树中价值最小的节点。也就是说,(默认的规则是小顶堆) - 树中的最小元素是根,索引为0
- 堆是实现优先级队列的一种常见方式。想要建立一个优先级队列,就实现
heap.Interface
接口,将(负)优先级作为Less
方法的排序,因此Push
增加项目,而Pop
从队列中删除最高优先级的项目。实例中包括这样一个实现:文件example_pq_test.go
中有完整的源代码
非常明显,这个包的核心就是提供了heap.Interface
接口,通过实现这个接口需要的排序、添加项目与删除项目的相关逻辑,就实现了一个小顶堆。
而在heap
包中,还实现了Init
、Push
、Pop
、Remove
、Fix
、up
、down
等方法,来提供调用的接口以及内部的实现
2 heap.Interface
非常简单:
// Interface类型描述了使用本包例程的需求
// 任何实现了Interface接口的类型都可以当做最小堆使用,
// (在调用Init后或数据为空或被排序后建立)并具有以下的不变性:
//
// !h.Less(j, i) for 0 <= i < h.Len() and
// 2*i+1 <= j <= 2*i+2 and j < h.Len()
type Interface interface {
sort.Interface
Push(x interface{}) // add x as element Len()
Pop() interface{} // remove and return element Len() - 1.
}
首先看看注释,有了自己手撕堆的经验就很容易看出,这个不变形表达的是:如果j是i的子节点,则Less(j, i)一定不成立。也就是说一定会维持小顶堆的结构。
Push
和Pop
的含义自然就是添加和删除元素,我们最要关注的是sort.Interface
这个接口,定义了哪些内容:
// Interface的实现可以通过这个包中的例程进行排序。
// 这些方法通过整数索引来引用底层集合的元素
type Interface interface {
// 元素的个数
Len() int
// Less 用于报告第i个元素是否排在第j个元素之前
//
// 如果Less(i,j)和Less(j,i)的返回值都是FALSE,则认为元素i与元素j相等
//
// Sort方法可能是不稳定的,而Stable方法是稳定的排序
//
// Less 必须描述一个可传递性的排序:
// - if both Less(i, j) and Less(j, k) are true, then Less(i, k) must be true as well.
// - if both Less(i, j) and Less(j, k) are false, then Less(i, k) must be false as well.
//
// 注意,浮点比较(float32或float64值的<操作符),当涉及到
// 非数字(NaN)值时,不是一个传递性排序。
// See Float64Slice.Less for a correct implementation for floating-point values.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
3 接口函数
Init
Init
函数建立了本包中其他例程所需要的堆不变形- 对于堆不变性来说,Init是等价的,只要堆不变性可能失效了,就可以调用它
- 复杂度为O(n),其中n = h.Len()
如果还记得我们的func Init(h Interface) {
// heapify
n := h.Len()
for i := n/2 - 1; i >= 0; i-- {
down(h, i, n)
}
}
buildMaxHeapify
函数,你就会发现,这俩就是一个意思,那么down
函数大概率就是我们的maxHeapify
啦Push
func Push(h Interface, x interface{}) {
h.Push(x)
up(h, h.Len()-1)
}