手撸golang 基本数据结构与算法 堆

Posted ioly

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手撸golang 基本数据结构与算法 堆相关的知识,希望对你有一定的参考价值。

手撸golang 基本数据结构与算法 堆

缘起

最近阅读<<我的第一本算法书>>(【日】石田保辉;宫崎修一)
本系列笔记拟采用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_structure

import (
    "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 heap
    hp := 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 samples
    rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
    samples := 13
    for i := 0;i < samples;i++ {
        hp.Push(rnd.Intn(100))
    }
    t.Log(hp)

    // test iterator
    iter := 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 sort
    prev := -1
    size := 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-9
    for 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_Heap
    heap_test.go:41: 
           0
          12,  36
          36,  17,  37,  53
          69,  37,  79,  22,  69,  37
    heap_test.go:54: 0,12,36,36,17,37,53,69,37,79,22,69,37
    heap_test.go:64: 0 = 0
    heap_test.go:64: 1 = 12
    heap_test.go:64: 2 = 17
    heap_test.go:64: 3 = 22
    heap_test.go:64: 4 = 36
    heap_test.go:64: 5 = 36
    heap_test.go:64: 6 = 37
    heap_test.go:64: 7 = 37
    heap_test.go:64: 8 = 37
    heap_test.go:64: 9 = 53
    heap_test.go:64: 10 = 69
    heap_test.go:64: 11 = 69
    heap_test.go:64: 12 = 79
    heap_test.go:84: 0,1,2,3,4,5,6,7,8,9
--- PASS: Test_Heap (0.00s)
PASS
ok      command-line-arguments  0.002s

IComparator.go

值大小比较器接口. 通过翻转比较函数, 也可以创建最大堆(顶点值最大).

package heap

type IComparator interface {
    Less(a interface{}, b interface{}) bool
}

IHeap.go

定义堆的接口

package heap

type IHeap interface {
    Size() int
    IsEmpty() bool
    IsNotEmpty() bool

    Push(value interface{})
    Pop() (error, interface{})

    Iterator() IIterator
    String() string
}

IIterator.go

堆迭代器接口

package heap

type IIterator interface {
    More() bool
    Next() (error,interface{})
}

tComparator.go

值大小比较器, 实现IComparator接口, 具体比较函数由外部传入

package heap

import "errors"

type FnLess func(a interface{}, b interface{}) bool

type 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 heap

import (
    "errors"
    "fmt"
    "strings"
)

type tArrayHeap struct {
    comparator IComparator
    items []interface{}
    size int
    version 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] = value
    me.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 - 1
    v = 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, pv
        me.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] = nil
    me.size--

    if me.IsEmpty() {
        return nil, top
    }

    me.items[0] = lv
    me.items[li] = nil

    me.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, pv
        me.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 := 0
    lines := make([]string, 0)
    lines = append(lines, "")

    for {
        n := 1<<level
        min := n - 1
        max := n + min - 1
        if 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 heap

import "errors"

type tHeapIterator struct {
    heap *tArrayHeap
    pos int
    version 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)

以上是关于手撸golang 基本数据结构与算法 堆的主要内容,如果未能解决你的问题,请参考以下文章

手撸golang 基本数据结构与算法 链表

手撸golang 基本数据结构与算法 数组

手撸golang 基本数据结构与算法 队列

手撸golang 基本数据结构与算法 栈

手撸golang 基本数据结构与算法 哈希表

手撸golang 基本数据结构与算法 二叉查找树