手撸golang 基本数据结构与算法 图的搜索 深度优先/广度优先

ioly · · 434 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

缘起

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

图的搜索

图的搜索指的就是从图的某一顶点开始,
通过边到达不同的顶点,
最终找到目标顶点的过程。

根据搜索的顺序不同,
图的搜索算法可分为“广度优先搜索”和“深度优先搜索”这两种。

虽然广度优先搜索和深度优先搜索在搜索顺序上有很大的差异,
但是在操作步骤上却只有一点不同,
那就是选择哪一个候补顶点作为下一个顶点的基准不同。

广度优先搜索选择的是最早成为候补的顶点(FIFO),
而深度优先搜索选择的则是最新成为候补的顶点(LIFO)。

摘自 <<我的第一本算法书>> 【日】石田保辉;宫崎修一

目标

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

设计

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

    • 从指定顶点出发, 访问所有可到达的顶点, 然后返回顶点数组
    • 使用哈希表记录已访问节点, 防止重复访问

单元测试

graph_visit_test.go

package graph

import (
    "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)
PASS
ok      command-line-arguments  0.002s

INode.go

顶点接口

package graph

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

IGraphVisitor.go

图的遍历器接口

package graph

type IGraphVisitor interface {
    Visit(root INode, policy VisitPolicy) []INode
}

type VisitPolicy int
const DFSPolicy VisitPolicy = 1
const BFSPolicy VisitPolicy = 2

tNode.go

顶点的实现

package graph

import (
    "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 graph

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

tLIFOQueue.go

LIFO堆栈, 实现INodeQueue接口

package graph


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


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

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


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

434 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传