缘起
最近阅读<<我的第一本算法书>>(【日】石田保辉;宫崎修一)
本系列笔记拟采用golang练习之
堆
堆是一种图的树形结构,被用于实现“优先队列”(priority queues)。优先队列是一种数据结构,可以自由添加数据,但取出数据时要从最小值开始按顺序取出。在堆中存储数据时必须遵守这样一条规则:子结点必定大于父结点。摘自 <<我的第一本算法书>> 【日】石田保辉;宫崎修一
补充知识
- 堆又名二叉堆, 是一种无序完全二叉树
- 所谓完全, 是指节点从上至下, 从左至右填充, 中间不会留有空隙
- 完全二叉树可以使用数组存放, 因为其父节点与左右子节点的索引存在线性关系, 以0下标为例
- parent.index = (child.index - 1) / 2, 如: 0 = (1 - 1) / 2, 0 = (2 - 1) / 2
- left.index = parent.index 2 + 1, 如: 1 = 0 2 + 1
- right.index = left.index + 1
- 添加数据时
- 先把数据添加到最末尾, 也就是堆的右下角
- 然后跟父节点比较大小, 如果小于父节点, 则交换之, 又名上浮
- 重复上浮的过程, 直到满足堆的规则: 父节点总是小于子节点
- 弹出数据时
- 总是弹出堆顶, 也就是最小值
- 然后把堆的末尾值, 放到堆顶. 移动末尾值, 不会留下中间间隙, 保持完全二叉树的状态
- 如果堆顶值大于某个子节点的值, 则与最小值节点交换, 又名下沉
- 重复下沉的过程, 直到满足堆的规则: 父节点总是小于子节点
目标
- 基于数组实现二叉堆, 并验证堆排序的功能
设计
- IComparator: 值大小比较器接口. 通过翻转比较函数, 也可以创建最大堆(顶点值最大).
- IHeap: 定义堆的接口
- IIterator: 堆迭代器接口
- tComparator: 值大小比较器, 实现IComparator接口, 具体比较函数由外部传入
- tArrayHeap: 二叉堆, 实现IHeap接口
- tHeapIterator: 堆迭代器, 实现IIterator接口
单元测试
heap_test.go, 验证二叉堆的基本功能, 并通过随机Push和有序Pop, 观察堆排序的效果
package data_structureimport ("fmt""learning/gooop/data_structure/heap""math/rand""strings""testing""time")func Test_Heap(t *testing.T) {fnAssertTrue := func(b bool, msg string) {if !b {panic(msg)}}fnLess := func(a interface{}, b interface{}) bool {i1 := a.(int)i2 := b.(int)return i1 < i2}comparator := heap.NewComparator(fnLess)// test empty heaphp := heap.NewArrayHeap(comparator)fnAssertTrue(hp.Size() == 0, "expecting size == 0")fnAssertTrue(hp.IsEmpty(), "expecting empty")fnAssertTrue(hp.Iterator().More() == false, "expecting !more")e,_ := hp.Pop()fnAssertTrue(e != nil, "expecting e != nil")// push random samplesrnd := rand.New(rand.NewSource(time.Now().UnixNano()))samples := 13for i := 0;i < samples;i++ {hp.Push(rnd.Intn(100))}t.Log(hp)// test iteratoriter := hp.Iterator()itemStrings := make([]string, 0)for i := 0;i < samples;i++ {e, v := iter.Next()fnAssertTrue(e == nil, "expecting e == nil")itemStrings = append(itemStrings, fmt.Sprintf("%v", v))}fnAssertTrue(iter.More() == false, "expecting !more")e,_ = iter.Next()fnAssertTrue(e != nil, "expecting e != nil")t.Log(strings.Join(itemStrings, ","))// test heap sortprev := -1size := hp.Size()for i := 0;i < size;i++ {e,v := hp.Pop()fnAssertTrue(e == nil, "expecting e == nil")n := v.(int)t.Logf("%d = %d", i, n)if prev >= 0 {fnAssertTrue(prev <= n, "expecting prev <= n")prev = n}prev = n}fnAssertTrue(hp.IsEmpty(), "expecting empty")// test 0-9for i := 0;i < 10;i++ {hp.Push(i)}itemStrings = make([]string, 0)for ;hp.IsNotEmpty(); {e,v := hp.Pop()fnAssertTrue(e == nil, "expecting e == nil")itemStrings = append(itemStrings, fmt.Sprintf("%v", v))}s := strings.Join(itemStrings, ",")t.Log(s)fnAssertTrue(s == "0,1,2,3,4,5,6,7,8,9", "expecting 0-9")}
测试输出
$ go test -v heap_test.go=== RUN Test_Heapheap_test.go:41:012, 3636, 17, 37, 5369, 37, 79, 22, 69, 37heap_test.go:54: 0,12,36,36,17,37,53,69,37,79,22,69,37heap_test.go:64: 0 = 0heap_test.go:64: 1 = 12heap_test.go:64: 2 = 17heap_test.go:64: 3 = 22heap_test.go:64: 4 = 36heap_test.go:64: 5 = 36heap_test.go:64: 6 = 37heap_test.go:64: 7 = 37heap_test.go:64: 8 = 37heap_test.go:64: 9 = 53heap_test.go:64: 10 = 69heap_test.go:64: 11 = 69heap_test.go:64: 12 = 79heap_test.go:84: 0,1,2,3,4,5,6,7,8,9--- PASS: Test_Heap (0.00s)PASSok command-line-arguments 0.002s
IComparator.go
值大小比较器接口. 通过翻转比较函数, 也可以创建最大堆(顶点值最大).
package heaptype IComparator interface {Less(a interface{}, b interface{}) bool}
IHeap.go
定义堆的接口
package heaptype IHeap interface {Size() intIsEmpty() boolIsNotEmpty() boolPush(value interface{})Pop() (error, interface{})Iterator() IIteratorString() string}
IIterator.go
堆迭代器接口
package heaptype IIterator interface {More() boolNext() (error,interface{})}
tComparator.go
值大小比较器, 实现IComparator接口, 具体比较函数由外部传入
package heapimport "errors"type FnLess func(a interface{}, b interface{}) booltype tComparator struct {fnLess FnLess}func NewComparator(fn FnLess) IComparator {if fn == nil {panic(gNullArgumentError)}return &tComparator{fnLess: fn,}}func (me *tComparator) Less(a interface{}, b interface{}) bool {if a == nil || b == nil {panic(gNullArgumentError)}return me.fnLess(a, b)}var gNullArgumentError = errors.New("null argument error")
tArrayHeap.go
二叉堆, 实现IHeap接口
package heapimport ("errors""fmt""strings")type tArrayHeap struct {comparator IComparatoritems []interface{}size intversion int64}func NewArrayHeap(comparator IComparator) IHeap {return &tArrayHeap{comparator: comparator,items: make([]interface{}, 0),size: 0,version: 0,}}func (me *tArrayHeap) Size() int {return me.size}func (me *tArrayHeap) IsEmpty() bool {return me.size <= 0}func (me *tArrayHeap) IsNotEmpty() bool {return !me.IsEmpty()}func (me *tArrayHeap) Push(value interface{}) {me.version++me.ensureSize(me.size + 1)me.items[me.size] = valueme.size++me.shiftUp(me.size - 1)me.version++}func (me *tArrayHeap) ensureSize(size int) {for ;len(me.items) < size; {me.items = append(me.items, nil)}}func (me *tArrayHeap) parentOf(i int) int {return (i - 1) / 2}func (me *tArrayHeap) leftChildOf(i int) int {return i*2 + 1}func (me *tArrayHeap) rightChildOf(i int) int {return me.leftChildOf(i) + 1}func (me *tArrayHeap) last() (i int, v interface{}) {if me.IsEmpty() {return -1, nil}i = me.size - 1v = me.items[i]return i,v}func (me *tArrayHeap) shiftUp(i int) {if i <= 0 {return}v := me.items[i]pi := me.parentOf(i)pv := me.items[pi]if me.comparator.Less(v, pv) {me.items[pi], me.items[i] = v, pvme.shiftUp(pi)}}func (me *tArrayHeap) Pop() (error, interface{}) {if me.IsEmpty() {return gNoMoreElementsError, nil}me.version++top := me.items[0]li, lv := me.last()me.items[0] = nilme.size--if me.IsEmpty() {return nil, top}me.items[0] = lvme.items[li] = nilme.shiftDown(0)me.version++return nil, top}func (me *tArrayHeap) shiftDown(i int) {pv := me.items[i]ok, ci, cv := me.minChildOf(i)if ok && me.comparator.Less(cv, pv) {me.items[i], me.items[ci] = cv, pvme.shiftDown(ci)}}func (me *tArrayHeap) minChildOf(p int) (ok bool, i int, v interface{}) {li := me.leftChildOf(p)if li >= me.size {return false, 0, nil}lv := me.items[li]ri := me.rightChildOf(p)if ri >= me.size {return true, li, lv}rv := me.items[ri]if me.comparator.Less(lv, rv) {return true, li, lv} else {return true, ri, rv}}func (me *tArrayHeap) Iterator() IIterator {return newHeapIterator(me)}func (me *tArrayHeap) String() string {level := 0lines := make([]string, 0)lines = append(lines, "")for {n := 1<<levelmin := n - 1max := n + min - 1if min >= me.size {break}line := make([]string, 0)for i := min;i <= max;i++ {if i >= me.size {break}line = append(line, fmt.Sprintf("%4d", me.items[i]))}lines = append(lines, strings.Join(line, ","))level++}return strings.Join(lines, "\n")}var gNoMoreElementsError = errors.New("no more elements")
tHeapIterator.go
堆迭代器, 实现IIterator接口
package heapimport "errors"type tHeapIterator struct {heap *tArrayHeappos intversion int64}func newHeapIterator(heap *tArrayHeap) IIterator {return &tHeapIterator{heap: heap,pos: 0,version: heap.version,}}func (me *tHeapIterator) More() bool {if me.version != me.heap.version {return false}return me.pos < me.heap.size}func (me *tHeapIterator) Next() (error, interface{}) {if me.version != me.heap.version {return gConcurrentModificationError, nil}if me.pos >= me.heap.size {return gNoMoreElementsError, nil}v := me.heap.items[me.pos]me.pos++return nil, v}var gConcurrentModificationError = errors.New("concurrent modification error")
(end)
