分享|关于 Golang 堆的娓娓道来
1761
2023.08.10
2023.08.10
发布于 未知归属地

Golang的堆

这篇文章将站在多种语言视角阐述堆的设计

泛型大关

对于阐述go的泛型

"go啥都好,就是没有泛型(^-^)"

对于go选手而言,go的堆是一个硬伤,在go众多简单的概念中,go的堆设计似乎格格不入,本处将以一个内置数据结构的设计阐述设计理由与使用方式.

先来看看常见的语言的堆用法(Python设计没啥代表性,因为python是解释性语言,它的类型一般运行时确定,而不需要泛型这种东西)
C++
std::priority_queue<int>a;
(go党一次暴击)
Java
var a=new PriorityQueue<Integer>();
(go党二次暴击)
C#

var a = new PriorityQueue<int, int>();
var b = new SortedSet<int>();
var c = new SortedDictionary<int, int>();
var d = new SortedList<int,int>();

(Go党:嗯???怎么平衡树都这么多)
PHP

$a=new SplMaxHeap();
$b=new SplMinHeap();

(Go党:"连PHP的都这么优雅吗?")

Scala
var a1=new java.util.PriorityQueue[Int]()
var a2=new java.util.TreeMap[Int,Int]()
var a3=new java.util.TreeSet[Int]()
var b1=scala.collection.mutable.PriorityQueue[Int]()
var b2=scala.collection.mutable.TreeMap[Int,Int]()
var b3=scala.collection.mutable.TreeSet[Int]()
var b4=scala.collection.mutable.SortedSet[Int]()
var b5=scala.collection.mutable.SortedMap[Int,Int]()

(Go党:"你在逗我吗???")
请问有没有哪个语言没有堆,有:Dart
(好开心,但是它有"SpalyTree",~???)
好叭,其实很多语言来说都是有堆的一个"易用级别实现",为啥诸如现代新型语言:rust、kotlin这些都能拥有如此好用的堆实现而go却每次需要玩家手动实现接口呢,这离不开一个概念:"泛型",对于go来说,早期没有泛型,导致抽象力度不够出现的梗都挺多的:"你知道吗?隔壁的go还要手写max函数呢"?????

先说说泛型的作用,我们以传统的静态编程语言Java为例:
8cdcbeec835d92991f1f86cdb0fc398.png
对于一个max函数来说很显然的是我们可以看到,对于每一种类型都实现了一种max.
8a1089cfcbefe178a0ec76f5f021416.png
我们可以很容易的看出,这两个max函数代码是一样的,只是类型不一样
C++
49332a64d426e87c69084ab5ffca709.png
我们可以很清楚看到,C++则是借助了模板来实现了一个max泛型函数.这个函数使得很多类型(实现了比较运算符)可以直接调用max函数

相信这边大部分小伙伴看出来了吧,泛型常常我们用字母T表示,这个T可以是任意类型,也可以是有一些限制的类型,比如实现了某些接口的类。当正儿八经调用运行时,这个T才会被确定是哪一个类型,而这样做的好处就是"复用性",更准确地来讲,抽象力度更强了

对于Java而言,泛型是作为编译时检查的一项内容,当编译以后确定好了类型以后,就会擦除泛型T变化为实际类型.这样一来其实我们可以把原本需要填int、long之类的目标统一用T代替,而这样一来,我们可以把它们归一化为一个函数.所以常常来说我们会发现Java的优先队列和C++的都需要加一个<类型>,用来表示实际的类型用于替代泛型
b80dc0dfa34d8fc0a6b07d65a52d493.png
07db13ce769ccc2bc6e28db51b97cc5.png
可以看到这些泛型不仅抽象力度好,还受到了类型限制(毕竟不是啥玩意都可以用来做比较的,咱得实现比较器接口是吧,或者有比较函数,重载运算符是吧)
(咱来瞧瞧Go的)
4495c1c0aa62e4c8ad6451ff3416ad8.png
333c322161579517137a0db4b9b564e.png
可以看到,哇呜,一共要实现五个接口函数!!!!!!
为啥要实现这五个函数呢:
有幸手写过堆的朋友:
洛谷堆模板题
可以发现
85af7a6d07d92e222be7b84e06beb7f.png
772bad86217b704642e5a9eb1b5a421.png
官方堆中最重要的两个函数是用的了这三个函数,而在push和pop中用到了
0677c1f8c66bea2f79912b3d972c9b7.png
这里的Push和Pop的实现和你写的实现是不一样的=>解释如下:

对于java、go一类的语言都有:实现接口就能干事的说法,而以这个堆为例:
你需要"先实现接口"再使用函数.接口中的push和pop的实现只是针对你实现它的类而言,
并不是堆本身的push和pop,通过源码我们可以看到heap.Push(你所实现接口的结构体实例,元素)最后会调"你所实现接口的实例的Push"
ad8bdef1348edf14bed7de3461c3855.png

进入主题

对于golang不得不提提一个特别的example
12afa5822b2ece3da7f5bbbf626e2e2.png
官方是写了两个例子IntHeap和一个普通的优先队列的例子("方便copy")

Go
// This example demonstrates an integer heap built using the heap interface.
package heap_test

import (
	"container/heap"
	"fmt"
)

// An IntHeap is a min-heap of ints.
type IntHeap []int

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 (h *IntHeap) Push(x any) {
	// Push and Pop use pointer receivers because they modify the slice's length,
	// not just its contents.
	*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
}

// This example inserts several ints into an IntHeap, checks the minimum,
// and removes them in order of priority.
func Example_intHeap() {
	h := &IntHeap{2, 1, 5}
	heap.Init(h)
	heap.Push(h, 3)
	fmt.Printf("minimum: %d\n", (*h)[0])
	for h.Len() > 0 {
		fmt.Printf("%d ", heap.Pop(h))
	}
	// Output:
	// minimum: 1
	// 1 2 3 5
}

读官方的IntHeap实现就会发觉,行吧非得手搓轮子是吧>>>
1c0c3b486bab71dec9d9f44b3a0c292.png
4ca5795a27cf06e4c86084c4453a294.png
cbb3ef5d060447354d31d2f4487521b.png
你闲逛于需要实现的接口所在的包,忽然发现了"哇,金色传说!!!"
有三个常用的切片已经为你实现好了三个接口函数,你只需要实现Push和Pop
在此之前,我们有必要过一遍golang的一些必知必会

Golang接收器

  1. 和传统的java这类语言不一样的是,golang又重新捡起了被现代一些语言所屏蔽的"指针"类型,而这类指针也不再是像C++一样的"裸指针",而是类似于C++引用效果一个的"安全指针",当然可以通过unsafe包求调用一个裸指针了.对于此,golang设计了"接收者类型"这样用于让耦合度足够低的操作.
    Go
    func (this A)Run(){
        xxxxx
    }
    func (this *A)Run(){
        xxxxx
    }
    区别很简单.接收者本质上来说和大部分语言的this、self是如出一辙的.只是添加了类型限定.而恰好有不同于其他语言的"指针实例".二者的差别仔细想下指针的作用不难明白:一个实例拷贝,一个是实例本身.很显而易见的是,后者带指针类型的就是我们常用的this、self等自身实例.那前者常常用于什么场景呢?答案是:设计只读字段.那设计出来的字段不希望被某个函数修改时,可以为这个函数的接受者设计为非指针类型(当然咱只能说算一个应用吧,功能有限),二者区别是很简单的
  2. 当然是语法糖咯.有个很有意思的语法糖.首先先来说说C++里面大家熟知的一个语法糖.当类型为结构体指针时,我们如果按照指针的常见写法来写.
    (*this).字段通常来说结构体指针先解指针再拿字段,如果这个结构体嵌套了不少层这玩意,岂不是要打好多好多好多"括号和解指针",所以有个非常常见的语法糖this->字段应运而生.呼~美观多了.帮助自动解指针,golang也有个非常非常非常"不错的语法糖",那就是无论接收器是结构体还是结构体指针,无论是当前变量为结构体变量还是结构体指针变量,咱都一个语法"this.字段",自动解指针.
    Go
    type A struct {
    }
    
    func (curr *A) Run() {
    
    }
    func test() {
        t1 := A{}
        t2 := &A{}
        (*t2).Run()
        t2.Run()
        t1.Run()
    }
    听上去有点nice,就是有时候比较不容易分清这个变量是啥类型了(没事,咱有ide,手动划去~
    !!!!:一个小重点:Golang切片本质上来说是数组的指针,所以常常的,切片传参可以当作指针传参,而不是拷贝数组传参(copy了一份地址,指向对象未变)

复习完毕,正式解析

开始正式解析这五个接口函数的作用

  1. Len():该函数用于获取序列的长度,当然常用的就是切片序列了

  2. Swap(i,j int):该函数的参数为序列的下标,交换两个数,那么很显然的是,利用golang的a,b=b,a轻松完成.提一嘴功能此函数在堆代码中主要是交换两个节点完成上下沉浮,有兴趣的可以在洛谷堆模板中学习手写堆,其实很短滴

  3. Less(i,j int)bool:参数依旧是序列的两个下标,灰常灰常灰常经典的Cmp函数.相信不少的C++小伙伴会用到手写Cmp,Java小伙伴会用到实现比较器接口,使用匿名内部类,Python小伙伴则是使用lambda表达式的key函数sort(key=lambda:一个优先级),Less函数功能正是如此手写Cmp

  4. Push(x any),any其实就是咱最神乎其神的"空接口"了,在golang当作空接口就是一个"Bug"存在,万物即我,我即万物.(等等,这不就是泛型T吗,Java的Object等基类的效果吗?手动删掉.any是当golang来到1.18版本以后终于推出的"泛型"(终于有了,咱站起来了)的一个特别的东西.其实,其实,其实看看源码吧:

    Go
    type any = interface{}

    纳尼?就这吗?别说,空接口名字挺长的,any也不错...对于转换空接口,噢不,any而言成为某个类型我们常常用类型断言(跟强制转化恰不多)

    var a=b.(转换的类型)还是有点高大上滴!!!,b是any类型.Push作用就是-------难以启齿,其实就是append(手动删去
    官方的IntHeap的Push实现:

    Go
    func (h *IntHeap) Push(x any) {
        // Push and Pop use pointer receivers because they modify the slice's length,
        // not just its contents.
        *h = append(*h, x.(int))
    }

    搞一个Push的名字让别人以为和堆的Push有啥关联(^v^)

  5. Pop()any:等等,这么说来,Pop就是RmoveLast???(咱高大上点,那名字谁都看出来啥意思,Pop多智慧呀,切~),官方实现:

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

这么说来???看到这的你可能会说就这,我原来被糊弄到了???

呼呼呼,咱继续往下说.

既然如此,那么我们的关注点来到了接收器类型,和实现接口的实现"结构体",好拗口啊,还是喜欢Java的实现类这个名字.
type IntHeap []int???官方实现???屏幕前的你缓缓打出问好,啊就这吗???不是结构体吗?
(这么喜欢结构体?拍桌,拍桌,拍桌,那就手写一个吧)
结构体应当由两部分,一部分实现嵌套接口的三个函数,一部分实现Push和Pop

Go
type Heap struct {
	ans []int
}

func (h *Heap) Len() int {
}

func (h *Heap) Less(i, j int) bool {
}

func (h *Heap) Swap(i, j int) {
}

func (h *Heap) Push(x any)  {
	
}
func (h *Heap) Pop()any{
	
}

???听你这么一说,好像我自己都会了.easy(手动划去.

常常来说,对于初学者我们接收者全部设置为指针类型就行,对于"高手"(说的就是你们这些go的爱好者),当然就是根据理解的写出

Go
type Heap struct {
	ans []int
}

func (h Heap) Len() int {
}

func (h Heap) Less(i, j int) bool {
}

func (h Heap) Swap(i, j int) {
}

func (h *Heap) Push(x any) {

}
func (h *Heap) Pop() any {

}

和官方代码如出一辙的代码.为啥前三个函数要用非指针呢.因为结构体的字段是指针(切片),所以外层的Swap操作切片的还是原切片.Soga!!!
那我们还是以全指针版本讲解吧:("当观众不是高手是吧",手动划去,防止一些朋友debug到疯)

  1. Len
    Go
    func (h *Heap) Len() int {
        return len(h.ans)
    }
    就这?
  2. Less
    Go
    func (h *Heap) Less(i, j int) bool {
        return h.ans[i]<h.ans[j]
    }
    就这.
  3. Swap
    Go
    func (h *Heap) Swap(i, j int) {
        h.ans[i], h.ans[j] = h.ans[j], h.ans[i]
    }
    就这~语气逐渐上扬
  4. Push
    Go
    func (h *Heap) Push(x any) {
        h.ans = append(h.ans, x.(int))
    }
    略微思索,我的ans是[]int,所以any应该往int转,就这啊.
  5. Pop
    Go
    func (h *Heap) Pop() any {
        old := h.ans
        n := len(old)
        top := old[n-1]
        h.ans = old[0 : n-1]
        return top
    }
    低头思索,如何删掉数组最后一个元素,并返回(这不是比乐扣的easy题还easy,手动删去
    py党说这个pop名字怎么和我们的列表的pop一个样啊,啊?因为切片木有pop函数得手写.到此,你会大声喊出来,就这啊~~~
    至此,go的堆你已经会了~,来实践用一下
Go
package main

import (
	"bufio"
	"container/heap"
	. "fmt"
	"io"
	"os"
	"runtime/debug"
)

type Heap struct {
	ans []int
}

func NewHeap() *Heap {
	return &Heap{}
}

func (h *Heap) Len() int {
	return len(h.ans)
}

func (h *Heap) Less(i, j int) bool {
	return h.ans[i] < h.ans[j]
}

func (h *Heap) Swap(i, j int) {
	h.ans[i], h.ans[j] = h.ans[j], h.ans[i]
}

func (h *Heap) Push(x any) {
	h.ans = append(h.ans, x.(int))
}
func (h *Heap) Pop() any {
	old := h.ans
	n := len(old)
	top := old[n-1]
	h.ans = old[0 : n-1]
	return top
}
func Solve(_r io.Reader, _w io.Writer) {
	//in := bufio.NewReader(_r)
	out := bufio.NewWriter(_w)
	defer out.Flush()
	//-----------------------------------------------------
	var a = NewHeap() //这是一个IntMinHeap,更改Less即可变为最大堆
	heap.Push(a, 12)  //肿么这么像python
	heap.Push(a, 87879)
	heap.Push(a, -8989)
	heap.Push(a, -889)
	heap.Push(a, -89)
	heap.Push(a, 97789) //随心所欲造数据
	for a.Len() > 0 {
		Fprintln(out, heap.Pop(a)) //呜~好像和python一样的
	}
}
func main() {
	debug.SetGCPercent(-1)
	Solve(os.Stdin, os.Stdout)
}

至此,你已经会堆了,而结构体元素版本,你也只需要更改Less的书写:例如常见的[]Pair,你只需要更改Less函数即可

看看拔高

(心想:又会了一个有b格的东西,真有b格,咱go玩家就是要不惧困难,手动划去)

Go
type Heap struct {
	ans sort.IntSlice
}

func NewHeap() *Heap {
	return &Heap{}
}

func (h *Heap) Len() int {
	return h.Len()
}

func (h *Heap) Less(i, j int) bool {
	return h.Less(i,j)
}

func (h *Heap) Swap(i, j int) {
	h.Swap(i,j)
}

func (h *Heap) Push(x any) {
	h.ans = append(h.ans, x.(int))
}
func (h *Heap) Pop() any {
	old := h.ans
	n := len(old)
	top := old[n-1]
	h.ans = old[0 : n-1]
	return top
}

啊,这不是???反复调API,隔这搁那呢,还不如我自己写的(有用滴就不错了)
言归正传,在新一代的golang版本,由于增加了泛型参数,而golang的接收器对象有个明确的规定"无法使用原生本地对象".好费解的话,其实就是无法像kotlin一样扩展函数(噫噫噫,java和c++之类的不是也不行吗?它们不行,为啥我们不能行一下,手动划去).众所周知,编译器是很笨的(不对,很聪明的)
3f26e9e85db96405a14c414dd2e4707.png

生气!!!!
5a1e19d56b457ddbf64a09bacf7401e.png

生气++++
69edaef07c8e6aae3e7d1f7273d1084.png

what???这么好糊弄
这里其实涉及到了~int,扩展int类型,有兴趣的可以看看
所以我们可以通过这样的方式,不仅可以调用原生的函数,还能写一些新的小玩意

Go的新时代,泛型

Cmp包:

Go
package cmp

type Ordered interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
		~float32 | ~float64 |
		~string
}
func Less[T Ordered](x, y T) bool {
	return (isNaN(x) && !isNaN(y)) || x < y
}

func Compare[T Ordered](x, y T) int {
	xNaN := isNaN(x)
	yNaN := isNaN(y)
	if xNaN && yNaN {
		return 0
	}
	if xNaN || x < y {
		return -1
	}
	if yNaN || x > y {
		return +1
	}
	return 0
}

func isNaN[T Ordered](x T) bool {
	return x != x
}

对于新版本的Golang而言,拥有了cmp.Ordered的泛型限定,凡是由这种泛型限定的,都可以直接调用<、>、==、!=这些运算符(这不就是默认运算符的实现吗???),不得不提一嘴,新版的Go泛型不要小尖尖,采用中括号[int],真不是看Scala的多跟着搞嘛(呜~)
所以说吗?整点活,来写一个通用结构体的泛型板子:

回忆下C++大哥,好像有点麻烦,好像咱go的泛型没那么厉害.看看Java大哥,比较器接口??这个简单,咱不叫Cmp了,多捞啊,叫Less函数

Go
type Cmp[T any] interface {
	Less(T) bool
}

???为啥接口名叫Cmp(我说的是函数名嘿嘿)
那么这个很显然的是,这个接口有个比较函数,这个则是实际代码过程中需要实现的

Go
type Heap[T Cmp[T]] struct {
	ans []T
}

当当当当,全新Heap闪亮登场.T类型是一个可比较类型即可.
剩下的???剩下的一个样,都看到这了!!!

Go
func (curr *Heap[T]) Len() int {
	return len(curr.ans)
}
func (curr *Heap[T]) Swap(x int, y int) {
	curr.ans[x], curr.ans[y] = curr.ans[y], curr.ans[x]
}
func (curr *Heap[T]) Less(i, j int) bool {
	return curr.ans[i].Less(curr.ans[j])
}
func (curr *Heap[T]) Push(x any) {
	curr.ans = append(curr.ans, x.(T))
}

func (curr *Heap[T]) Pop() any {
	old := curr.ans
	n := len(old)
	x := old[n-1]
	curr.ans = old[0 : n-1]
	return x
}
func NewHeap[T Cmp[T]]() *Heap[T] {
	return &Heap[T]{}
}

easy.来用一下Pair版本(咱这个也搞个泛型呗,当然不太熟的朋友,搞个最普通的int版本也是很简单的)

Go
Go
type Pair[T cmp.Ordered] struct {
	x, y T
}

func NewPair[T cmp.Ordered](x T, y T) Pair[T] {
	return Pair[T]{x, y}
}
func (curr Pair[T]) Less(other Pair[T]) bool {
	if curr.x == other.x {
		return curr.y < other.y
	}
	return curr.x < other.x
}

豁???这不就是平常我们最常用的吗?没有任何学习压力啊,你不写这玩意,我们平常也会用到
PS:有的小伙伴会问(非go选手)为啥实现接口没有啥关键字implements这种.那是因为golang的解耦性很高,只要你实现了接口所有函数,就算实现了接口,很随意吧
呼呼,使用一下???

Go
Go
package main

import (
	"bufio"
	"container/heap"
	"fmt"
	"io"
	"os"
	"runtime/debug"
)

type Cmp[T any] interface {
	Less(T) bool
}
type Heap[T Cmp[T]] struct {
	ans []T
}

func (curr *Heap[T]) Len() int {
	return len(curr.ans)
}
func (curr *Heap[T]) Swap(x int, y int) {
	curr.ans[x], curr.ans[y] = curr.ans[y], curr.ans[x]
}
func (curr *Heap[T]) Less(i, j int) bool {
	return curr.ans[i].Less(curr.ans[j])
}
func (curr *Heap[T]) Push(x any) {
	curr.ans = append(curr.ans, x.(T))
}

func (curr *Heap[T]) Pop() any {
	old := curr.ans
	n := len(old)
	x := old[n-1]
	curr.ans = old[0 : n-1]
	return x
}
func NewHeap[T Cmp[T]]() *Heap[T] {
	return &Heap[T]{}
}

type Pair struct {
	x, y int
}

func NewPair(x int, y int) Pair {
	return Pair{x, y}
}
func (curr Pair) Less(other Pair) bool {
	if curr.x == other.x {
		return curr.y < other.y
	}
	return curr.x < other.x
}
func Solve(_r io.Reader, _w io.Writer) {
	//in := bufio.NewReader(_r)
	out := bufio.NewWriter(_w)
	defer out.Flush()
	//-----------------------------------------------------
	var a = NewHeap[Pair]()
	heap.Push(a, NewPair(154, 21))
	heap.Push(a, NewPair(154, 31))
	heap.Push(a, NewPair(28, 29))
	heap.Push(a, NewPair(456, 2321))
	heap.Push(a, NewPair(456, 232))
	for a.Len() > 0 {
		var a = heap.Pop(a).(Pair)
		fmt.Println(a.x, a.y)
	}
}
func main() {
	debug.SetGCPercent(-1)
	Solve(os.Stdin, os.Stdout)
}

这个时候你只想大喊一声easy~
等等,那int咋办???
噢~忘了刚刚骗计算机的花招了?(其实也称不上骗~~)

Go
type M int

func (curr M) Less(other M) bool {
	return curr < other
}

(好短~)

Go
package main

import (
	"bufio"
	"cmp"
	"container/heap"
	"fmt"
	"io"
	"os"
	"runtime/debug"
)

type Cmp[T any] interface {
	Less(T) bool
}
type Heap[T Cmp[T]] struct {
	ans []T
}

func (curr *Heap[T]) Len() int {
	return len(curr.ans)
}
func (curr *Heap[T]) Swap(x int, y int) {
	curr.ans[x], curr.ans[y] = curr.ans[y], curr.ans[x]
}
func (curr *Heap[T]) Less(i, j int) bool {
	return curr.ans[i].Less(curr.ans[j])
}
func (curr *Heap[T]) Push(x any) {
	curr.ans = append(curr.ans, x.(T))
}

func (curr *Heap[T]) Pop() any {
	old := curr.ans
	n := len(old)
	x := old[n-1]
	curr.ans = old[0 : n-1]
	return x
}
func NewHeap[T Cmp[T]]() *Heap[T] {
	return &Heap[T]{}
}

type Pair[T cmp.Ordered] struct {
	x, y T
}

func NewPair[T cmp.Ordered](x T, y T) Pair[T] {
	return Pair[T]{x, y}
}
func (curr Pair[T]) Less(other Pair[T]) bool {
	if curr.x == other.x {
		return curr.y < other.y
	}
	return curr.x < other.x
}

type M int

func (curr M) Less(other M) bool {
	return curr < other
}

func Solve(_r io.Reader, _w io.Writer) {
	//in := bufio.NewReader(_r)
	out := bufio.NewWriter(_w)
	defer out.Flush()
	//-----------------------------------------------------
	var a = NewHeap[M]()
	heap.Push(a, M(454))
	heap.Push(a, M(456))
	heap.Push(a, M(487))
	heap.Push(a, M(787978))
	heap.Push(a, M(787798897))
	for a.Len() > 0 {
		fmt.Println(heap.Pop(a))
	}
}
func main() {
	debug.SetGCPercent(-1)
	Solve(os.Stdin, os.Stdout)
}

是不是感觉很easy啊,目前的常见算法平台atcoder已经开始测试新语言版本(go是1.20!!!)

Go
// The max built-in function returns the largest value of a fixed number of
// arguments of [cmp.Ordered] types. There must be at least one argument.
// If T is a floating-point type and any of the arguments are NaNs,
// max will return NaN.
func max[T cmp.Ordered](x T, y ...T) T

// The min built-in function returns the smallest value of a fixed number of
// arguments of [cmp.Ordered] types. There must be at least one argument.
// If T is a floating-point type and any of the arguments are NaNs,
// min will return NaN.
func min[T cmp.Ordered](x T, y ...T) T

// The print built-in function formats its arguments in an
// implementation-specific way and writes the result to standard error.
// Print is useful for bootstrapping and debugging; it is not guaranteed
// to stay in the language.
func print(args ...Type)

// The println built-in function formats its arguments in an
// implementation-specific way and writes the result to standard error.
// Spaces are always added between arguments and a newline is appended.
// Println is useful for bootstrapping and debugging; it is not guaranteed
// to stay in the language.
func println(args ...Type)

???金色传说,新版本的Go是很值得去研究和玩的,越来越多的东西加入(说不定哪天手写的旋转treap舍去,官方内置了平衡树),最后的最后,再贴一个官方的优先队列的实现:

Go
// Copyright 2012 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.

// This example demonstrates a priority queue built using the heap interface.
package heap_test

import (
	"container/heap"
	"fmt"
)

// An Item is something we manage in a priority queue.
type Item struct {
	value    string // The value of the item; arbitrary.
	priority int    // The priority of the item in the queue.
	// The index is needed by update and is maintained by the heap.Interface methods.
	index int // The index of the item in the heap.
}

// A PriorityQueue implements heap.Interface and holds Items.
type PriorityQueue []*Item

func (pq PriorityQueue) Len() int { return len(pq) }

func (pq PriorityQueue) Less(i, j int) bool {
	// We want Pop to give us the highest, not lowest, priority so we use greater than here.
	return pq[i].priority > pq[j].priority
}

func (pq PriorityQueue) Swap(i, j int) {
	pq[i], pq[j] = pq[j], pq[i]
	pq[i].index = i
	pq[j].index = j
}

func (pq *PriorityQueue) Push(x any) {
	n := len(*pq)
	item := x.(*Item)
	item.index = n
	*pq = append(*pq, item)
}

func (pq *PriorityQueue) Pop() any {
	old := *pq
	n := len(old)
	item := old[n-1]
	old[n-1] = nil  // avoid memory leak
	item.index = -1 // for safety
	*pq = old[0 : n-1]
	return item
}

// update modifies the priority and value of an Item in the queue.
func (pq *PriorityQueue) update(item *Item, value string, priority int) {
	item.value = value
	item.priority = priority
	heap.Fix(pq, item.index)
}

// This example creates a PriorityQueue with some items, adds and manipulates an item,
// and then removes the items in priority order.
func Example_priorityQueue() {
	// Some items and their priorities.
	items := map[string]int{
		"banana": 3, "apple": 2, "pear": 4,
	}

	// Create a priority queue, put the items in it, and
	// establish the priority queue (heap) invariants.
	pq := make(PriorityQueue, len(items))
	i := 0
	for value, priority := range items {
		pq[i] = &Item{
			value:    value,
			priority: priority,
			index:    i,
		}
		i++
	}
	heap.Init(&pq)

	// Insert a new item and then modify its priority.
	item := &Item{
		value:    "orange",
		priority: 1,
	}
	heap.Push(&pq, item)
	pq.update(item, item.value, 5)

	// Take the items out; they arrive in decreasing priority order.
	for pq.Len() > 0 {
		item := heap.Pop(&pq).(*Item)
		fmt.Printf("%.2d:%s ", item.priority, item.value)
	}
	// Output:
	// 05:orange 04:pear 03:banana 02:apple
}

现在来看是不是很easy啊?就在上文提到的example_pq_test.go文件中
制作不易,如有口误,欢迎评论区指正~

评论 (11)