缘起
最近阅读<<我的第一本算法书>>(【日】石田保辉;宫崎修一)
本系列笔记拟采用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_GraphVisitgraph_visit_test.go:53: testing dfs visitorgraph_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 visitorgraph_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() stringAppend(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 stringchildren []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() boolPush(node INode)Poll() (bool, INode)}
tLIFOQueue.go
LIFO堆栈, 实现INodeQueue接口
package graphtype tLIFOQueue struct {nodes []INodecapacity intsize int}func newLIFOQueue() iNodeQueue {it := &tLIFOQueue{}it.Clear()return it}func (me *tLIFOQueue) Clear() {me.nodes = make([]INode, 0)me.capacity = 0me.size = 0}func (me *tLIFOQueue) Push(node INode) {me.ensureSpace(1)me.nodes[me.size] = nodeme.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] = nilreturn true, it}
tFIFOQeuue.go
FIFO队列, 实现INodeQueue接口
package graphtype tFIFOQueue struct {nodes []INodecapacity intrindex intwindex int}func newFIFOQueue() iNodeQueue {it := &tFIFOQueue{}it.Clear()return it}func (me *tFIFOQueue) Clear() {me.nodes = make([]INode, 0)me.capacity = 0me.rindex = -1me.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] = nilif me.rindex > me.capacity / 2 {size := me.size()offset := me.rindex + 1for i := 0;i < size;i++ {me.nodes[i], me.nodes[i + offset] = me.nodes[i + offset], nil}me.rindex -= offsetme.windex -= offset}return true, it}
tGraphVisitor.go
遍历器, 实现IGraphVisitor接口
- 从指定顶点出发, 访问所有可到达的顶点, 然后返回顶点数组
- 使用哈希表记录已访问节点, 防止重复访问 ```go 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()] = trueresult = 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)
