Search depth first / breadth first for basic data structure and algorithm diagram of manual golang

Time:2021-10-17

origin

Recently read < < my first algorithm book > > ([Japan] Ishida Baohui; Miyazaki Xiuyi)
This series of notes is intended to use golang exercises

Graph search

Graph search refers to starting from a vertex of the graph,
Reaching different vertices through edges,
The process of finally finding the target vertex.

Depending on the order of search,
The search algorithm of graph can be divided into "breadth first search" and "depth first search".

Although breadth first search and depth first search differ greatly in search order,
But there is only one difference in the operation steps,
That is, which candidate vertex is selected as the benchmark of the next vertex is different.

Breadth first search selects the first candidate vertex (FIFO),
The depth first search selects the latest candidate vertex (LIFO).

From < < my first algorithm book > > [Japan] Ishida Baohui; Miyazaki Xiuyi

target

  • By replacing the selection methods of candidate nodes (LIFO, FIFO), depth first search and breadth first search are realized and verified respectively

Design

  • Inode: vertex interface
  • Igraphvisitor: traversal interface of graph
  • Tnode: implementation of vertex
  • Inodequeue: candidate node queue. Different selection methods of candidate nodes determine whether depth first or breadth first
  • Tlifoqueue: LIFO stack, which implements the inodequeue interface
  • Tfifoqeue: FIFO queue, which implements the inodequeue interface
  • Tgraphvisitor: ergodic, implementing igraphvisitor interface,

    • Starting from the specified vertex, access all reachable vertices, and then return the vertex array
    • Use a hash table to record visited nodes to prevent repeated access

unit testing

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")
}

Test output

$ 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

Vertex interface

package graph

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

IGraphVisitor.go

Graph traverser interface

package graph

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

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

tNode.go

Implementation of vertex

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

Candidate node queue interface. Different selection methods of candidate nodes determine whether depth first or breadth first

package graph

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

tLIFOQueue.go

LIFO stack to implement inodequeue interface

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 queue, which implements the inodequeue interface

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

Ergodic implement igraphvisitor interface

  • Starting from the specified vertex, access all reachable vertices, and then return the vertex array
  • Use a hash table to record visited nodes to prevent repeated access
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)