缘起

最近浏览<<我的第一本算法书>>(【日】石田保辉;宫崎修一)
本系列笔记拟采纳golang练习之

图的搜寻

图的搜寻指的就是从图的某一顶点开始,通过边达到不同的顶点,最终找到指标顶点的过程。依据搜寻的程序不同,图的搜索算法可分为“广度优先搜寻”和“深度优先搜寻”这两种。尽管广度优先搜寻和深度优先搜寻在搜寻程序上有很大的差别,然而在操作步骤上却只有一点不同,那就是抉择哪一个候补顶点作为下一个顶点的基准不同。广度优先搜寻抉择的是最早成为候补的顶点(FIFO),而深度优先搜寻抉择的则是最新成为候补的顶点(LIFO)。摘自 <<我的第一本算法书>> 【日】石田保辉;宫崎修一

指标

  • 通过替换候选节点的抉择形式(LIFO, FIFO), 别离实现并验证深度优先搜寻和广度优先搜寻

设计

  • INode: 顶点接口
  • IGraphVisitor: 图的遍历器接口
  • tNode: 顶点的实现
  • iNodeQueue: 候选节点队列. 候选节点的抉择形式不同, 决定了是深度优先还是广度优先.
  • tLIFOQueue: LIFO堆栈, 实现iNodeQueue接口
  • tFIFOQeuue: FIFO队列, 实现iNodeQueue接口
  • tGraphVisitor: 遍历器, 实现IGraphVisitor接口,

    • 从指定顶点登程, 拜访所有可达到的顶点, 而后返回顶点数组
    • 应用哈希表记录已拜访节点, 避免反复拜访

单元测试

graph_visit_test.go

package graphimport (    "fmt"    "learning/gooop/graph"    "strings"    "testing")func Test_GraphVisit(t *testing.T) {    fnAssertTrue := func(b bool, msg string) {        if !b {            t.Fatal(msg)        }    }    root := graph.NewNode("ROOT")    a := graph.NewNode("a")    b := graph.NewNode("b")    root.Append(a)    root.Append(b)    ac := graph.NewNode("ac")    ad := graph.NewNode("ad")    a.Append(ac)    a.Append(ad)    be := graph.NewNode("be")    bf := graph.NewNode("bf")    b.Append(be)    b.Append(bf)    acg := graph.NewNode("acg")    ach := graph.NewNode("ach")    ac.Append(acg)    ac.Append(ach)    bfi := graph.NewNode("bfi")    bfj := graph.NewNode("bfj")    bf.Append(bfi)    bf.Append(bfj)    fnVisitGraph := func(policy graph.VisitPolicy) string {        lines := make([]string, 0)        nodes := graph.GraphVisitor.Visit(root, policy)        for _,it := range nodes {            lines = append(lines, fmt.Sprintf("%s", it))        }        return strings.Join(lines, " ")    }    t.Log("testing dfs visitor")    dfs := fnVisitGraph(graph.DFSPolicy)    t.Log(dfs)    fnAssertTrue(dfs == "ROOT-[a,b] b-[be,bf] bf-[bfi,bfj] bfj-[] bfi-[] be-[] a-[ac,ad] ad-[] ac-[acg,ach] ach-[] acg-[]", "expecting dfs result")    t.Log("testing bfs visitor")    bfs := fnVisitGraph(graph.BFSPolicy)    t.Log(bfs)    fnAssertTrue(bfs == "ROOT-[a,b] a-[ac,ad] b-[be,bf] ac-[acg,ach] ad-[] be-[] bf-[bfi,bfj] acg-[] ach-[] bfi-[] bfj-[]", "expecting bfs result")}

测试输入

$ go test -v graph_visit_test.go === RUN   Test_GraphVisit    graph_visit_test.go:53: testing dfs visitor    graph_visit_test.go:55: ROOT-[a,b] b-[be,bf] bf-[bfi,bfj] bfj-[] bfi-[] be-[] a-[ac,ad] ad-[] ac-[acg,ach] ach-[] acg-[]    graph_visit_test.go:58: testing bfs visitor    graph_visit_test.go:60: ROOT-[a,b] a-[ac,ad] b-[be,bf] ac-[acg,ach] ad-[] be-[] bf-[bfi,bfj] acg-[] ach-[] bfi-[] bfj-[]--- PASS: Test_GraphVisit (0.00s)PASSok      command-line-arguments  0.002s

INode.go

顶点接口

package graphtype INode interface {    ID() string    Append(child INode)    Children() []INode}

IGraphVisitor.go

图的遍历器接口

package graphtype IGraphVisitor interface {    Visit(root INode, policy VisitPolicy) []INode}type VisitPolicy intconst DFSPolicy VisitPolicy = 1const BFSPolicy VisitPolicy = 2

tNode.go

顶点的实现

package graphimport (    "fmt"    "strings")type tNode struct {    id string    children []INode}func NewNode(id string) INode {    return &tNode{        id: id,        children: make([]INode, 0),    }}func (me *tNode) ID() string {    return me.id}func (me *tNode) Append(child INode) {    me.children = append(me.children, child)}func (me *tNode) Children() []INode {    return me.children}func (me *tNode) String() string {    items := make([]string, len(me.children))    for i,it := range me.children {        items[i] = it.ID()    }    return fmt.Sprintf("%v-[%s]", me.id, strings.Join(items, ","))}

iNodeQueue.go

候选节点队列接口. 候选节点的抉择形式不同, 决定了是深度优先还是广度优先.

package graphtype iNodeQueue interface {    Clear()    Empty() bool    Push(node INode)    Poll() (bool, INode)}

tLIFOQueue.go

LIFO堆栈, 实现INodeQueue接口

package graphtype tLIFOQueue struct {    nodes []INode    capacity int    size int}func newLIFOQueue() iNodeQueue {    it := &tLIFOQueue{}    it.Clear()    return it}func (me *tLIFOQueue) Clear() {    me.nodes = make([]INode, 0)    me.capacity = 0    me.size = 0}func (me *tLIFOQueue) Push(node INode) {    me.ensureSpace(1)    me.nodes[me.size] = node    me.size++}func (me *tLIFOQueue) ensureSpace(space int) {    for me.capacity < me.size + space {        me.nodes = append(me.nodes, nil)        me.capacity++    }}func (me *tLIFOQueue) Empty() bool {    return me.size <= 0}func (me *tLIFOQueue) Poll() (bool, INode) {    if me.Empty() {        return false, nil    }    me.size--    it := me.nodes[me.size]    me.nodes[me.size] = nil    return true, it}

tFIFOQeuue.go

FIFO队列, 实现INodeQueue接口

package graphtype tFIFOQueue struct {    nodes []INode    capacity int    rindex int    windex int}func newFIFOQueue() iNodeQueue {    it := &tFIFOQueue{}    it.Clear()    return it}func (me *tFIFOQueue) Clear() {    me.nodes = make([]INode, 0)    me.capacity = 0    me.rindex = -1    me.windex = -1}func (me *tFIFOQueue) size() int {    return me.windex - me.rindex}func (me *tFIFOQueue) Empty() bool {    return me.size() <= 0}func (me *tFIFOQueue) Push(node INode) {    me.ensureSpace(1)    me.windex++    me.nodes[me.windex] = node}func (me *tFIFOQueue) ensureSpace(size int) {    for me.capacity < me.windex + size + 1 {        me.nodes = append(me.nodes, nil)        me.capacity++    }}func (me *tFIFOQueue) Poll() (bool, INode) {    if me.Empty() {        return false, nil    }    me.rindex++    it := me.nodes[me.rindex]    me.nodes[me.rindex] = nil    if me.rindex > me.capacity / 2 {        size := me.size()        offset := me.rindex + 1        for i := 0;i < size;i++ {            me.nodes[i], me.nodes[i + offset] = me.nodes[i + offset], nil        }        me.rindex -= offset        me.windex -= offset    }    return true, it}

tGraphVisitor.go

遍历器, 实现IGraphVisitor接口

  • 从指定顶点登程, 拜访所有可达到的顶点, 而后返回顶点数组
  • 应用哈希表记录已拜访节点, 避免反复拜访
package graphtype tGraphVisitor struct {}func newGraphVisitor() IGraphVisitor {    return &tGraphVisitor{    }}func (me *tGraphVisitor) createQueue(policy VisitPolicy) iNodeQueue {    switch policy {    case BFSPolicy:        return newFIFOQueue()    case DFSPolicy:        return newLIFOQueue()    default:        panic("unsupported policy")    }}func (me *tGraphVisitor) Visit(root INode, policy VisitPolicy) []INode {    queue := me.createQueue(policy)    queue.Push(root)    visited := make(map[string]bool, 0)    result := make([]INode, 0)    for !queue.Empty() {        _,node := queue.Poll()        visited[node.ID()] = true        result = append(result, node)        children := node.Children()        if children != nil {            for _,it := range children {                ok,_ := visited[it.ID()]                if ok {                    continue                }                queue.Push(it)            }        }    }    return result}var GraphVisitor = newGraphVisitor()

(end)