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