作为go语言初学者,非常有必要多写写代码来熟悉go的语法和风格,本文是关于go语言实现最小堆,就不对最小堆进行赘述了,具体的构建原理可以参考我的这篇博客
下面直接上代码~
package main
import "fmt"
type Node struct {
Value int
}
// 用于构建结构体切片为最小堆,需要调用down函数
func Init(nodes []Node) {
for i := len(nodes)/2-1; i >=0; i-- {
down(nodes,i,len(nodes))
}
}
// 需要down(下沉)的元素在切片中的索引为i,n为heap的长度,将该元素下沉到该元素对应的子树合适的位置,从而满足该子树为最小堆的要求
func down(nodes []Node, i, n int) {
parent := i
child := 2*parent+1
temp := nodes[parent].Value
for{
if child < n {
if child + 1 < n && nodes[child].Value > nodes[child+1].Value {
child++
}
if temp <= nodes[child].Value{
break
}
nodes[parent].Value = nodes[child].Value
parent = child
child = child*2+1
}else{
break
}
}
nodes[parent].Value = temp
}
// 用于保证插入新元素(j为元素的索引,切片末尾插入,堆底插入)的结构体切片之后仍然是一个最小堆
func up(nodes []Node, j int) {
child := j
parent := (j-1) / 2
for{
if child == 0 {
break
}
if nodes[parent].Value < nodes[child].Value {
break
}
temp := nodes[child].Value
nodes[child].Value = nodes[parent].Value
nodes[parent].Value = temp
child = parent
parent = (parent-1)/2
}
}
// 弹出最小元素,并保证弹出后的结构体切片仍然是一个最小堆,第一个返回值是弹出的节点的信息,第二个参数是Pop操作后得到的新的结构体切片
func Pop(nodes []Node) (Node, []Node) {
min := nodes[0]
nodes[0].Value = nodes[len(nodes)-1].Value
nodes = nodes[:len(nodes)-1]
down(nodes,0,len(nodes)-1)
return min, nodes
}
// 保证插入新元素时,结构体切片仍然是一个最小堆,需要调用up函数
func Push(node Node, nodes []Node) []Node {
nodes = append(nodes,node)
up(nodes,len(nodes)-1)
return nodes
}
// 移除切片中指定索引的元素,保证移除后结构体切片仍然是一个最小堆
func Remove(nodes []Node, node Node) []Node {
for i := 0; i < len(nodes); i++ {
if node.Value == nodes[i].Value {
nodes[i].Value = nodes[len(nodes)-1].Value
nodes = nodes[0:len(nodes)-1]
down(nodes,0,len(nodes)-1)
break
}
}
return nodes
}
//我添加的函数,用于打印切片
func Display(nodes []Node) {
for _, ele := range nodes {
fmt.Printf("%d ", ele.Value)
}
fmt.Printf("\n")
}
func main() {
nodes := []Node{
Node{3},
Node{6},
Node{9},
Node{1},
Node{2},
Node{5},
Node{8},
Node{4},
Node{7},
}
fmt.Printf("Before test\n")
Display(nodes)
fmt.Printf("Testing Init() and down()\n")
Init(nodes)
Display(nodes)
fmt.Printf("Testing up() with adding 0\n")
node_add := Node{0}
nodes = append(nodes,node_add)
up(nodes,9)
Display(nodes)
fmt.Printf("Testing Pop() with popping 0\n")
min, nodes := Pop(nodes)
fmt.Printf("Minimum :%d\n",min)
Display(nodes)
fmt.Printf("Testing Remove() with removing 5\n")
node_remove := Node{5}
nodes = Remove(nodes,node_remove)
Display(nodes)
fmt.Printf("Testing Push()\n")
array := []Node{
Node{9},
Node{7},
Node{6},
}
node5 := Node{5}
node4 := Node{4}
node3 := Node{3}
node2 := Node{2}
node1 := Node{1}
array = Push(node5,array)
array = Push(node4,array)
array = Push(node3,array)
array = Push(node2,array)
array = Push(node1,array)
Display(array)
}
GoConvey是一款针对Golang的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多 Web 界面特性。使用go test
指令就可以直接运行。
在测试之前,我们需要修改上面部分函数的返回值(这里就不放代码了),返回构造好的最小堆,以便使用GoConvey进行测试。
首先将miniHeap.go打包,go build之后应该会在pkg中生成.a文件
然后在同一目录下,增加miniHeap_test.go测试文件:
package miniHeap
import (
"testing"
."github.com/smartystreets/goconvey/convey"
)
func TestInit(t *testing.T){
Convey("testing Init()",t,func(){
So(Init([]Node{
Node{3},
Node{6},
Node{9},
Node{1},
Node{2},
Node{5},
Node{8},
Node{4},
Node{7},
}),ShouldResemble,[]Node{
Node{1},
Node{2},
Node{5},
Node{4},
Node{3},
Node{9},
Node{8},
Node{6},
Node{7},
})
})
}
func TestUp(t *testing.T){
Convey("testing up()",t,func(){
So(up([]Node{
Node{1},
Node{2},
Node{5},
Node{4},
Node{3},
Node{9},
Node{8},
Node{6},
Node{7},
Node{0},
},9),ShouldResemble,
[]Node{
Node{0},
Node{1},
Node{5},
Node{4},
Node{2},
Node{9},
Node{8},
Node{6},
Node{7},
Node{3},
})
})
}
func TestPop(t *testing.T){
Convey("testing Pop()",t,func(){
So(Pop([]Node{
Node{0},
Node{1},
Node{5},
Node{4},
Node{2},
Node{9},
Node{8},
Node{6},
Node{7},
Node{3},
}),ShouldResemble,
[]Node{
Node{1},
Node{2},
Node{5},
Node{4},
Node{3},
Node{9},
Node{8},
Node{6},
Node{7},
})
})
}
func TestPush(t *testing.T){
Convey("testing Push()",t,func(){
So(Push(Node{0},[]Node{
Node{1},
Node{2},
Node{5},
Node{4},
Node{3},
Node{9},
Node{8},
Node{6},
Node{7},
}),ShouldResemble,
[]Node{
Node{0},
Node{1},
Node{5},
Node{4},
Node{2},
Node{9},
Node{8},
Node{6},
Node{7},
Node{3},
})
})
}
func TestRemove(t *testing.T){
Convey("testing Remove()",t,func(){
So(Remove([]Node{
Node{1},
Node{2},
Node{5},
Node{4},
Node{3},
Node{9},
Node{8},
Node{6},
Node{7},
},Node{1}),ShouldResemble,
[]Node{
Node{2},
Node{3},
Node{5},
Node{4},
Node{7},
Node{9},
Node{8},
Node{6},
})
})
}
下面是运行结果
由于我也是初学者,有的地方还不甚明白(比如原Pop函数,返回一个切片和一个结构体,我不知道怎么写断言,只好去掉返回值中的结构体),写法也比较笨重,希望大佬们可以指点指点~