GO语言heap剖析及利用heap实现优先级队列

GO语言heap剖析

本节内容

  1. heap使用
  2. heap提供的方法
  3. heap源码剖析
  4. 利用heap实现优先级队列

1. heap使用

在go语言的标准库container中,实现了三中数据类型:heap,list,ring,list在前面一篇文章中已经写了,现在要写的是heap(堆)的源码剖析。

首先,学会怎么使用heap,第一步当然是导入包了,代码如下:

package main

import (
    "container/heap"
    "fmt"
)

这个堆使用的数据结构是最小二叉树,即根节点比左边子树和右边子树的所有值都小。源码里面只是实现了一个接口,它的定义如下:

type Interface interface {
    sort.Interface
    Push(x interface{}) // add x as element Len()
    Pop() interface{}   // remove and return element Len() - 1.
}

从这个接口可以看出,其继承了sort.Interface接口,那么sort.Interface的定义是什么呢?源码如下:

type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    // Less reports whether the element with
    // index i should sort before the element with index j.
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}

也就是说,我们要使用go标准库给我们提供的heap,那么必须自己实现这些接口定义的方法,需要实现的方法如下:

  • Len() int
  • Less(i, j int) bool
  • Swap(i, j int)
  • Push(x interface{})
  • Pop() interface{}

实现了这五个方法的数据类型才能使用go标准库给我们提供的heap,下面简单示例为定义一个IntHeap类型,并实现上面五个方法。

type IntHeap []int  // 定义一个类型

func (h IntHeap) Len() int { return len(h) }  // 绑定len方法,返回长度
func (h IntHeap) Less(i, j int) bool {  // 绑定less方法
    return h[i] < h[j]  // 如果h[i]<h[j]生成的就是小根堆,如果h[i]>h[j]生成的就是大根堆
}
func (h IntHeap) Swap(i, j int) {  // 绑定swap方法,交换两个元素位置
    h[i], h[j] = h[j], h[i]
}

func (h *IntHeap) Pop() interface{} {  // 绑定pop方法,从最后拿出一个元素并返回
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

func (h *IntHeap) Push(x interface{}) {  // 绑定push方法,插入新元素
    *h = append(*h, x.(int))
}

针对IntHeap实现了这五个方法之后,我们就可以使用heap了,下面是具体使用方法:

func main() {
    h := &IntHeap{2, 1, 5, 6, 4, 3, 7, 9, 8, 0}  // 创建slice
    heap.Init(h)  // 初始化heap
    fmt.Println(*h)
    fmt.Println(heap.Pop(h))  // 调用pop
    heap.Push(h, 6)  // 调用push
    fmt.Println(*h)
    for len(*h) > 0 {
        fmt.Printf("%d ", heap.Pop(h))
    }

}
输出结果:
[0 1 3 6 2 5 7 9 8 4]
0
[1 2 3 6 4 5 7 9 8 6]
1 2 3 4 5 6 6 7 8 9 

上面就是heap的使用了。

2. heap提供的方法

heap提供的方法不多,具体如下:

h := &IntHeap{3, 8, 6}  // 创建IntHeap类型的原始数据
func Init(h Interface)  // 对heap进行初始化,生成小根堆(或大根堆)
func Push(h Interface, x interface{})  // 往堆里面插入内容
func Pop(h Interface) interface{}  // 从堆顶pop出内容
func Remove(h Interface, i int) interface{}  // 从指定位置删除数据,并返回删除的数据
func Fix(h Interface, i int)  // 从i位置数据发生改变后,对堆再平衡,优先级队列使用到了该方法

3. heap源码剖析

heap的内部实现,是使用最小(最大)堆,索引排序从根节点开始,然后左子树,右子树的顺序方式。 内部实现的down和up分别表示对堆中的某个元素向下保证最小(最大)堆和向上保证最小(最大)堆。

当往堆中插入一个元素的时候,这个元素插入到最右子树的最后一个节点中,然后调用up向上保证最小(最大)堆。

当要从堆中推出一个元素的时候,先吧这个元素和右子树最后一个节点交换,然后弹出最后一个节点,然后对root调用down,向下保证最小(最大)堆。

好了,开始分析源码:

首先,在使用堆之前,必须调用它的Init方法,初始化堆,生成小根(大根)堆。Init方法源码如下:

// A heap must be initialized before any of the heap operations
// can be used. Init is idempotent with respect to the heap invariants
// and may be called whenever the heap invariants may have been invalidated.
// Its complexity is O(n) where n = h.Len().
//
func Init(h Interface) {
    // heapify
    n := h.Len()  // 获取数据的长度
    for i := n/2 - 1; i >= 0; i-- {  // 从长度的一半开始,一直到第0个数据,每个位置都调用down方法,down方法实现的功能是保证从该位置往下保证形成堆
        down(h, i, n)
    }
}

接下来看down的源码:

func down(h Interface, i0, n int) bool {
    i := i0  // 中间变量,第一次存储的是需要保证往下需要形成堆的节点位置
    for {  // 死循环
        j1 := 2*i + 1  // i节点的左子孩子
        if j1 >= n || j1 < 0 { // j1 < 0 after int overflow  // 保证其左子孩子没有越界
            break
        }
        j := j1 // left child  // 中间变量j先赋值为左子孩子,之后j将被赋值为左右子孩子中最小(大)的一个孩子的位置
        if j2 := j1 + 1; j2 < n && !h.Less(j1, j2) {
            j = j2 // = 2*i + 2  // right child
        }  // 这之后,j被赋值为两个孩子中的最小(大)孩子的位置(最小或最大由Less中定义的决定)
        if !h.Less(j, i) {
            break
        }  // 若j大于(小于)i,则终止循环
        h.Swap(i, j)  // 否则交换i和j位置的值
        i = j  // 令i=j,继续循环,保证j位置的子数是堆结构
    }
    return i > i0
}

这是建立堆的核心代码,其实,down并不能完全保证从某个节点往下每个节点都能保持堆的特性,只能保证某个节点的值如果不满足堆的性质,则将该值与其孩子交换,直到该值放到适合的位置,保证该值及其两个子孩子满足堆的性质。

但是,如果是通过Init循环调用down将能保证初始化后所有的节点都保持堆的特性,这是因为循环开始的i := n/2 - 1的取值位置,将会取到最大的一个拥有孩子节点的节点,并且该节点最多只有两个孩子,并且其孩子节点是叶子节点,从该节点往前每个节点如果都能保证down的特性,则整个列表也就符合了堆的性质了。

同样,有down就有up,up保证的是某个节点如果向上没有保证堆的性质,则将其与父节点进行交换,直到该节点放到某个特定位置保证了堆的性质。代码如下:

func up(h Interface, j int) {
    for {  // 死循环
        i := (j - 1) / 2 // parent  // j节点的父节点
        if i == j || !h.Less(j, i) {  // 如果越界,或者满足堆的条件,则结束循环
            break
        }
        h.Swap(i, j)  // 否则将该节点和父节点交换
        j = i  // 对父节点继续进行检查直到根节点
    }
}

以上两个方法就是最核心的方法了,所有暴露出来的方法无非就是对这两个方法进行的封装。我们来看看以下这些方法的源码:

func Push(h Interface, x interface{}) {
    h.Push(x)  // 将新插入进来的节点放到最后
    up(h, h.Len()-1)  // 确保新插进来的节点网上能保证堆结构
}

func Pop(h Interface) interface{} {
    n := h.Len() - 1  // 把最后一个节点和第一个节点进行交换,之后,从根节点开始重新保证堆结构,最后把最后那个节点数据丢出并返回
    h.Swap(0, n)
    down(h, 0, n)
    return h.Pop()
}

func Remove(h Interface, i int) interface{} {
    n := h.Len() - 1  pop只是remove的特殊情况,remove是把i位置的节点和最后一个节点进行交换,之后保证从i节点往下及往上都保证堆结构,最后把最后一个节点的数据丢出并返回
    if n != i {
        h.Swap(i, n)
        down(h, i, n)
        up(h, i)
    }
    return h.Pop()
}

func Fix(h Interface, i int) {
    if !down(h, i, h.Len()) {  // i节点的数值发生改变后,需要保证堆的再平衡,先调用down保证该节点下面的堆结构,如果有位置交换,则需要保证该节点往上的堆结构,否则就不需要往上保证堆结构,一个小小的优化
        up(h, i)
    }
}

以上就是go里面的heap所有的源码了,我也就不贴出完整版源码了,以上理解全部基于个人的理解,如有不当之处,还望批评指正。

4. 利用heap实现优先级队列

既然用到了heap,那就用heap实现一个优先级队列吧,这个功能是很好的一个功能。
源码如下:

package main

import (
    "container/heap"
    "fmt"
)

type Item struct {
    value    string // 优先级队列中的数据,可以是任意类型,这里使用string
    priority int    // 优先级队列中节点的优先级
    index    int    // index是该节点在堆中的位置
}

// 优先级队列需要实现heap的interface
type PriorityQueue []*Item

// 绑定Len方法
func (pq PriorityQueue) Len() int {
    return len(pq)
}

// 绑定Less方法,这里用的是小于号,生成的是小根堆
func (pq PriorityQueue) Less(i, j int) bool {
    return pq[i].priority < pq[j].priority
}

// 绑定swap方法
func (pq PriorityQueue) Swap(i, j int) {
    pq[i], pq[j] = pq[j], pq[i]
    pq[i].index, pq[j].index = i, j
}

// 绑定put方法,将index置为-1是为了标识该数据已经出了优先级队列了
func (pq *PriorityQueue) Pop() interface{} {
    old := *pq
    n := len(old)
    item := old[n-1]
    *pq = old[0 : n-1]
    item.index = -1
    return item
}

// 绑定push方法
func (pq *PriorityQueue) Push(x interface{}) {
    n := len(*pq)
    item := x.(*Item)
    item.index = n
    *pq = append(*pq, item)
}

// 更新修改了优先级和值的item在优先级队列中的位置
func (pq *PriorityQueue) update(item *Item, value string, priority int) {
    item.value = value
    item.priority = priority
    heap.Fix(pq, item.index)
}

func main() {
    // 创建节点并设计他们的优先级
    items := map[string]int{"二毛": 5, "张三": 3, "狗蛋": 9}
    i := 0
    pq := make(PriorityQueue, len(items)) // 创建优先级队列,并初始化
    for k, v := range items {             // 将节点放到优先级队列中
        pq[i] = &Item{
            value:    k,
            priority: v,
            index:    i}
        i++
    }
    heap.Init(&pq) // 初始化堆
    item := &Item{ // 创建一个item
        value:    "李四",
        priority: 1,
    }
    heap.Push(&pq, item)           // 入优先级队列
    pq.update(item, item.value, 6) // 更新item的优先级
    for len(pq) > 0 {
        item := heap.Pop(&pq).(*Item)
        fmt.Printf("%.2d:%s index:%.2d\n", item.priority, item.value, item.index)
    }
}

输出结果:
03:张三 index:-01
05:二毛 index:-01
06:李四 index:-01
09:狗蛋 index:-01

转载于:https://www.cnblogs.com/huxianglin/p/6925119.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值