前言
堆,是计算机科学中的一种特别的完全二叉树。若父节点的值恒小于等于子节点的值,此堆称为最小堆(min heap);反之,若母节点的值恒大于等于子节点的值,此堆称为最大堆(max heap)。在堆中最顶端的那一个节点,称作 根节点(root node),根节点本身没有 父节点(parent node)。堆通常是一个可以被看做一棵树的数组对象。在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。
优先级队列 是计算机科学中的一类抽象数据类型。优先队列中的每个元素都有各自的优先级,优先级最高的元素最先得到服务;优先级相同的元素按照其在优先队列中的顺序得到服务。优先队列往往用堆来实现。
Golang实现一:根据原理简单实现
package minheap
import (
"container/heap"
"fmt"
"math"
"github.com/pkg/errors"
)
/*
* @CreateTime: 2021/7/6 21:57
* @Author: hujiaming
* @Description: Golang实现最小堆
*/
var ErrMinHeapEmpty = errors.New("minHeap is empty")
const HeapHeadTag int64 = math.MinInt64
type MinHeap struct {
elements []int64
}
// NewMinHeap 创建一个最小堆实例
func NewMinHeap() *MinHeap {
return &MinHeap{elements: []int64{HeapHeadTag}}
}
/*
Add 将一个元素添加到最小堆中,并且添加后要使其满足最小堆的特性
首先将该元素插入到数组最后,然后对这最后一个元素进行 “上浮” 操作:
该元素与父元素进行大小比较,如果小于父元素,则和父元素交换位置,如此循环,直到 到达堆顶 或 子元素小于父元素。
*/
func (mh *MinHeap) Add(v int64) {
// 1. 先将元素插在数组最后面
mh.elements = append(mh.elements, v)
// 2. 将最后一个元素上浮,使其符合最小堆的性质。其实是为 v 找位置
i := len(mh.elements) - 1
for ; mh.elements[i/2] > v; i /= 2 {
mh.elements[i] = mh.elements[i/2]
}
mh.elements[i] = v
}
/*
PopMin 弹出堆中最小的元素
对最小堆而言,移除元素,只能移除堆顶(最小值)的元素。
首先,移除堆顶元素,然后将最后一个元素放在堆顶,之后对这第一个元素进行 “下沉” 操作:
将此元素与两个子节点元素比较,如果当前结点大于两个子节点,则与较小的子节点交换位置,如此循环,直到 到达叶子结点 或 小于较小子节点。
*/
func (mh *MinHeap) PopMin() (int64, error) {
if mh.IsEmpty() {
return 0, ErrMinHeapEmpty
}
res := mh.elements[1]
last := mh.elements[len(mh.elements)-1]
// idx 表示最后一个元素应该在的位置
var idx int
for idx = 1; idx*2 < len(mh.elements); {
// 找出子节点中较小的元素的 index
minChildIdx := idx * 2
if minChildIdx < len(mh.elements)-1 && mh.elements[minChildIdx+1] < mh.elements[minChildIdx] {
minChildIdx++
}
// 当前结点 大于 较小子节点,和这个较小子节点交换位置,继续循环
if last > mh.elements[minChildIdx] {
mh.elements[idx] = mh.elements[minChildIdx]
idx = minChildIdx
continue
}
break
}
mh.elements[idx] = last
mh.elements = mh.elements[:len(mh.elements)-1]
return res, nil
}
// PeekHead 只返回堆顶元素(最小值),不进行下沉操作
func (mh *MinHeap) PeekHead() (int64, error) {
if mh.IsEmpty() {
return 0, ErrMinHeapEmpty
}
return mh.elements[1], nil
}
// IsEmpty 最小堆是否是空的
func (mh *MinHeap) IsEmpty() bool {
if len(mh.elements) == 0 || (len(mh.elements) == 1 && mh.elements[0] == HeapHeadTag) {
return true
}
return false
}
// Length 返回最小堆中的元素个数
func (mh *MinHeap) Length() int {
return len(mh.elements) - 1
}
// Print 打印代表最小堆的数组
func (mh *MinHeap) Print() {
fmt.Println(mh.elements[1:])
}
Test 如下:
func TestMinHeap(t *testing.T) {
mh := NewMinHeap()
mh.Add(4)
mh.Add(2)
mh.Add(7)
mh.Add(9)
mh.Add(1)
mh.Add(5)
mh.Add(10)
mh.Add(3)
mh.Add(2)
mh.Print()
for !mh.IsEmpty() {
fmt.Println(mh.PopMin())
}
assert.Equal(t, mh.Length(), 0)
}
// 输出
/*
[1 2 5 2 4 7 10 9 3]
1 <nil>
2 <nil>
2 <nil>
3 <nil>
4 <nil>
5 <nil>
7 <nil>
9 <nil>
10 <nil>
*/
Golang 实现二:实现标准库 heap.Interface 接口
先看下标准库中的 Interface,位置在 container/heap/heap.go:
// The Interface type describes the requirements
// for a type using the routines in this package.
// Any type that implements it may be used as a
// min-heap with the following invariants (established after
// Init has been called or if the data is empty or sorted):
//
// !h.Less(j, i) for 0 <= i < h.Len() and 2*i+1 <= j <= 2*i+2 and j < h.Len()
//
// Note that Push and Pop in this interface are for package heap's
// implementation to call. To add and remove things from the heap,
// use heap.Push and heap.Pop.
type Interface interface {
sort.Interface
Push(x interface{}) // add x as element Len()
Pop() interface{} // remove and return element Len() - 1.
}
// An implementation of Interface can be sorted by the routines in this package.
// The methods refer to elements of the underlying collection by integer index.
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with index i
// must sort before the element with index j.
//
// If both Less(i, j) and Less(j, i) are false,
// then the elements at index i and j are considered equal.
// Sort may place equal elements in any order in the final result,
// while Stable preserves the original input order of equal elements.
//
// Less must describe a transitive ordering:
// - 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.
//
// Note that floating-point comparison (the < operator on float32 or float64 values)
// is not a transitive ordering when not-a-number (NaN) values are involved.
// 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)
}
我们以此为基础,实现一个 优先级队列:
package priorityqueen
type Item struct {
value int64 // 实际值
priority int64 // 优先级
index int // 当前 item 在数组中的 index
}
// PriorityQueen 表示优先级队列
type PriorityQueen []*Item
func (mh2 PriorityQueen) Len() int {
return len(mh2)
}
func (mh2 PriorityQueen) Less(i, j int) bool {
return mh2[i].priority < mh2[j].priority
}
func (mh2 PriorityQueen) Swap(i, j int) {
mh2[i], mh2[j] = mh2[j], mh2[i]
mh2[i].index = i
mh2[j].index = j
}
// Push 将 x 添加到数组最后
func (mh2 *PriorityQueen) Push(x interface{}) {
l := len(*mh2)
c := cap(*mh2)
if l+1 > c {
cmh2 := make([]*Item, l, c/2)
copy(*mh2, cmh2)
*mh2 = cmh2
}
*mh2 = (*mh2)[:l+1]
item := (x).(*Item)
item.index = l
(*mh2)[l] = item
}
// Pop 返回数组最后一个元素
func (mh2 *PriorityQueen) Pop() interface{} {
l := len(*mh2)
c := cap(*mh2)
if l < c/2 && c > 25 {
cmh2 := make([]*Item, l, c/2)
copy(cmh2, *mh2)
*mh2 = cmh2
}
item := (*mh2)[l-1]
item.index = -1 // for safety
*mh2 = (*mh2)[:l-1]
return item
}
// PopHead 弹出堆顶元素
func (mh2 *PriorityQueen) PopHead() *Item {
if mh2.Len() == 0 {
return nil
}
item := (*mh2)[0]
heap.Remove(mh2, 0)
return item
}
// PopWithPriority 弹出优先级小于 maxP 的堆顶元素,如果没有,返回 nil 和 当前堆顶和maxP的距离
func (mh2 *PriorityQueen) PopWithPriority(maxP int64) (*Item, int64) {
if mh2.Len() == 0 {
return nil, 0
}
item := (*mh2)[0]
if item.priority > maxP {
return nil, item.priority - maxP
}
heap.Remove(mh2, 0)
return item, 0
}
// PeekHead 显示堆顶元素
func (mh2 *PriorityQueen) PeekHead() *Item {
if mh2.Len() == 0 {
return nil
}
heap.Init(mh2)
item := (*mh2)[0]
return item
}
测试一下:
func TestPriorityQueen(t *testing.T) {
items := make([]*Item, 0)
rand.Seed(time.Now().UnixNano())
for i := 0; i < 10; i++ {
v := rand.Int63n(100)
items = append(items, &Item{
value: v,
priority: v,
index: i,
})
}
q := PriorityQueen(items)
heap.Init(&q)
fmt.Println(q.PeekHead())
maxP := int64(50)
for _, i := range q {
if i.priority < maxP {
fmt.Println(fmt.Sprintf("p: %d, v: %d", i.priority, i.value))
}
}
fmt.Println("====")
for i := 0; i < 10; i++ {
item, _ := q.PopWithPriority(maxP)
if item != nil {
fmt.Println(item)
}
}
fmt.Println("====")
for {
item := q.PopHead()
if item == nil {
break
}
fmt.Println(item)
}
}
// 输出
/*
&{5 5 0}
p: 5, v: 5
p: 11, v: 11
p: 6, v: 6
p: 33, v: 33
====
&{5 5 -1}
&{6 6 -1}
&{11 11 -1}
&{33 33 -1}
&{50 50 -1}
====
&{52 52 -1}
&{73 73 -1}
&{85 85 -1}
&{97 97 -1}
&{99 99 -1}
*/
Golang 标准库 heap.Interface 源码解析
整个包的实现非常简洁,加上注释以及空行,整个文件才只有120 行:
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package heap provides heap operations for any type that implements
// heap.Interface. A heap is a tree with the property that each node is the
// minimum-valued node in its subtree.
//
// The minimum element in the tree is the root, at index 0.
//
// A heap is a common way to implement a priority queue. To build a priority
// queue, implement the Heap interface with the (negative) priority as the
// ordering for the Less method, so Push adds items while Pop removes the
// highest-priority item from the queue. The Examples include such an
// implementation; the file example_pq_test.go has the complete source.
//
package heap
import "sort"
// The Interface type describes the requirements
// for a type using the routines in this package.
// Any type that implements it may be used as a
// min-heap with the following invariants (established after
// Init has been called or if the data is empty or sorted):
//
// !h.Less(j, i) for 0 <= i < h.Len() and 2*i+1 <= j <= 2*i+2 and j < h.Len()
//
// Note that Push and Pop in this interface are for package heap's
// implementation to call. To add and remove things from the heap,
// use heap.Push and heap.Pop.
type Interface interface {
sort.Interface
Push(x interface{}) // add x as element Len()
Pop() interface{} // remove and return element Len() - 1.
}
// Init establishes the heap invariants required by the other routines in this package.
// Init is idempotent with respect to the heap invariants
// and may be called whenever the heap invariants may have been invalidated.
// The complexity is O(n) where n = h.Len().
func Init(h Interface) {
// heapify
n := h.Len()
// (n/2 - 1) 处的结点是最后一棵子树(没有孩子结点)的根节点
for i := n/2 - 1; i >= 0; i-- {
down(h, i, n)
}
}
// Push pushes the element x onto the heap.
// The complexity is O(log n) where n = h.Len().
func Push(h Interface, x interface{}) {
h.Push(x)
up(h, h.Len()-1)
}
// Pop removes and returns the minimum element (according to Less) from the heap.
// The complexity is O(log n) where n = h.Len().
// Pop is equivalent to Remove(h, 0).
func Pop(h Interface) interface{} {
n := h.Len() - 1
h.Swap(0, n)
down(h, 0, n)
return h.Pop()
}
// Remove removes and returns the element at index i from the heap.
// The complexity is O(log n) where n = h.Len().
func Remove(h Interface, i int) interface{} {
n := h.Len() - 1
if n != i {
h.Swap(i, n)
if !down(h, i, n) {
up(h, i)
}
}
return h.Pop()
}
// Fix re-establishes the heap ordering after the element at index i has changed its value.
// Changing the value of the element at index i and then calling Fix is equivalent to,
// but less expensive than, calling Remove(h, i) followed by a Push of the new value.
// The complexity is O(log n) where n = h.Len().
func Fix(h Interface, i int) {
if !down(h, i, h.Len()) {
up(h, i)
}
}
func up(h Interface, j int) {
for {
i := (j - 1) / 2 // parent
if i == j || !h.Less(j, i) {
break
}
h.Swap(i, j)
j = i
}
}
func down(h Interface, i0, n int) bool {
i := i0
for {
j1 := 2*i + 1
if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
break
}
j := j1 // left child
if j2 := j1 + 1; j2 < n && h.Less(j2, j1) {
j = j2 // = 2*i + 2 // right child
}
if !h.Less(j, i) {
break
}
h.Swap(i, j)
i = j
}
return i > i0
}
我们关注其中几个核心实现:
down(h Interface, idx, heapLen int)下沉操作:
首先,移除堆顶元素,然后将最后一个元素放在堆顶,之后对这第一个元素进行 “下沉” 操作:
将此元素与两个子节点元素比较,如果当前结点大于两个子节点,则与较小的子节点交换位置,如此循环,直到 到达叶子结点 或 小于较小子节点。
为什么元素 i 比它的两个子节点都小,就可以跳出循环,不再继续下去呢?这是由于,在Init函数中,第一个开始down的元素是第n/2 - 1个,可以保证总是从最后一棵子树开始down,因此可以保证Init->down时,如果元素i比它的两个子节点都小,那么该元素对应的子树,就是最小堆。
up(h Interface, curIdx int)上浮操作:
主要用在Push中,当我们向最小堆插入一个元素时,现将其插入到数组最后,之后进行上浮操作,此时的curIdx就是数组最后一个元素的index,即h.Len() - 1。当前元素与其父元素进行比较,如果当前元素小于父元素,则与父元素交换位置,如此往复,直到堆顶或者当前元素大于父元素。
