Go标准库的container
包提供了3个包,分别是heap
,list
,ring
,分别实现了堆,链表和环形链表。
heap
Go语言中堆的定义是一个接口。
type Interface interface {
sort.Interface
Push(x any) // add x as element Len()
Pop() any // remove and return element Len() - 1.
}
根据注释我们发现Push
的实现是要求在末尾插入,Pop
的实现要求删除末尾元素。sort.Interface
是另一个接口,定义了一个可排序的容器。
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)
}
接口中Less
方法的实现方式决定了排序方式,是增序还是降序。sort
包提供了3种默认实现:IntSlice
,Float64Slice
和StringSlice
。它们默认都是增序的,如果需要降序排序,可以使用Reverse
函数快速得到一个降序数组。
例子:
package main
import (
"fmt"
"sort"
)
func main() {
s1 := sort.IntSlice([]int{3, 1, 5, 2, 4})
sort.Sort(s1)
fmt.Println(s1)
s2 := sort.Reverse(s1)
sort.Sort(s2)
fmt.Println(s2)
}
// 输出
// [1 2 3 4 5]
// &{[5 4 3 2 1]}
因此我们可以借用InsSlice
快速实现一个int堆。
package main
import (
"container/heap"
"fmt"
"sort"
)
type IntHp struct { // 堆实现
sort.IntSlice
}
func (hp *IntHp) Push(v any) {
hp.IntSlice = append(hp.IntSlice, v.(int))
}
func (hp *IntHp) Pop() (v any) {
v = hp.IntSlice[hp.Len()-1]
hp.IntSlice = hp.IntSlice[:hp.Len()-1]
return
}
func main() {
var hp IntHp
heap.Push(&hp, 5)
fmt.Println(hp)
heap.Push(&hp, 3)
fmt.Println(hp)
heap.Push(&hp, 1)
fmt.Println(hp)
heap.Push(&hp, 10)
fmt.Println(hp)
fmt.Println(heap.Pop(&hp))
fmt.Println(hp)
fmt.Println(heap.Pop(&hp))
fmt.Println(hp)
}
/* 输出
{[5]}
{[3 5]}
{[1 5 3]}
{[1 5 3 10]}
1
{[3 5 10]}
3
{[5 10]}
*/
这样就实现了一个小顶堆。堆内的数据并不是严格排序的,只保证了最小的数在第一个,因此必须通过heap.Push
和heap.Pop
来操作堆,不能直接调用堆上的Push
和Pop
方法,这是一个比较容易出错的地方,特别是习惯了面对对象那一套的人。
如果要实现大顶堆,可以自己实现sort.Interface
接口的Less
方法,改成升序,这样就得到了一个大顶堆。
除了heap.Pop
删除堆顶,也可以使用heap.Remove
删除任意下标的元素。不管是哪种删除方式,其原理都是将要删除的元素交换到数组末尾,然后调用接口的Pop
方法删除末尾元素,因为删除数组末尾元素可以避免数组元素的移动。对数组来说,无论是插入还是删除,尾插/删总是最高效的。
Tips:这里补充一个小知识点,如果要实现高效的删除数组中的某个元素算法,可以将目标元素交换到末尾,然后删除末尾元素。
heap.Remove(hp, 1)
如果将非空数组转换成堆,堆内数据无序,我们有两种方式可以修正堆。第一种方式是使用heap.Init
,第二种方式是使用heap.Fix
。
func main() {
hp := IntHp{
[]int{3, 2, 1},
}
fmt.Println(hp)
heap.Init(&hp)
fmt.Println(hp)
hp.IntSlice[0], hp.IntSlice[1] = hp.IntSlice[1], hp.IntSlice[0]
fmt.Println(hp)
heap.Fix(&hp, 0)
fmt.Println(hp)
}
/* 输出
{[3 2 1]}
{[1 2 3]}
{[2 1 3]}
{[1 2 3]}
*/
维护堆的函数有两个,up
和donw
。up
将小元素"上浮",down
让大元素"下沉"。
up
的源码如下:
func up(h heap.Interface, j int) {
for { // i i+1 j
i := (j - 1) / 2 // |⬛⬛⬛|⬛|⬛⬛⬛|
if i == j || !h.Less(j, i) { // i == j || [i] <= [j]
break
}
h.Swap(i, j)
j = i
}
}
up
在将元素上浮时,并不是挨个比较上浮,而是向当前位置往前一半的地方上浮,所以堆的底层数组并不是有序的。使用up
维护堆的目的是不是让堆有序,而是让最小的元素始终是数组的第一个元素。
down
源码如下:
func down(h sort.Interface, i0, n int) bool {
i := i0
for { // i i+1 j1
j1 := 2*i + 1 // |⬛⬛⬛|⬛|⬛⬛⬛|⬛
if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
break
}
j := j1 // i i+1 j1 j2
if j2 := j1 + 1; j2 < n && h.Less(j2, j1) { // |⬛⬛⬛|⬛|⬛⬛⬛|⬛
j = j2 // = 2*i + 2 // j
}
if !h.Less(j, i) { // [i] <= [j]
break
}
h.Swap(i, j)
i = j
}
return i > i0
}
down
在将大元素下沉的时候也是按当前位置2倍的距离下沉,同样也不一定能让数组有序。参数i0
表示要下沉的元素位置,n
表示下沉的界限,超过该位置便不再下沉,注意这个位置是达不到的。down
的返回值表示有没有下沉,因为如果当前元素没有被下沉,就说明当前元素比后面的元素小,那么就需要考虑将当前元素上浮,具体可以看heap.Remove
的实现。
heap
提供的API如下:
API | 说明 |
---|---|
Init | 初始化堆 |
Fix | 修复堆 |
Push | 入堆 |
Pop | 删除堆顶 |
Remove | 删除指定下标的元素 |
list
标准库的list
是一个双向环链表。
链表节点定义如下:
type Element struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *Element
// The list to which this element belongs.
list *List
// The value stored with this element.
Value any
}
链表的定义如下:
type List struct {
root Element // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
}
List
可以开箱即用,也可以通过list.New()
返回一个初始化过的链表。
package main
import (
"container/list"
"fmt"
)
func main() {
var l1 list.List
l1.PushBack(1)
fmt.Println(l1)
l2 := list.New()
l2.PushFront("a")
fmt.Println(l2)
}
List
的API都非常简单。
API | 说明 |
---|---|
Front | 获取头节点 |
Back | 获取尾节点 |
PushFront | 头插入 |
PushFrontList | 头插入链表 |
PushBack | 尾插入 |
PushBackList | 尾插入链表 |
InsertAfter | 在节点后插入 |
InsterBefore | 在节点前插入 |
MoveAfter | 移动一个节点到另一个节点前 |
MoveBefore | 移动一个节点到另一个节点后 |
MoveToFront | 移动节点到链表头 |
MoveToBack | 移动节点到链表尾 |
Remove | 删除节点 |
Len | 获取链表长度 |
ring
Ring
是内置的环形链表,定义如下:
type Ring struct {
next, prev *Ring
Value any // for use by client; untouched by this library
}
使用Ring
建议通过ring.New
函数创建环形链表,并指定容量。Ring
提供的API也很简单。
API | 说明 |
---|---|
Do | 接受一个函数,遍历环形链表并应用该函数 |
Len | 返回环形链表长度 |
Next | 返回下一节点 |
Prev | 返回上一个节点 |
Move | 移动n步,负数向前移动,正数向后移动 |
Link | 将一个环形链表连接进来 |
UnLink | 从环形链表中分离出一个n节点的环形链表 |
注意哪些和移动相关的函数,它们会返回一个Ring
节点,我们用一个变量接收它。
例子:
package main
import (
"container/ring"
"fmt"
)
func main() {
show := func(x any) {
fmt.Printf("%v ", x)
}
r := ring.New(2)
r.Value = 1
r.Next().Value = 2
r.Do(show)
fmt.Println()
r = r.Move(-1)
r.Do(show)
fmt.Println()
}
/* 输出
1 2
2 1
*/