缘起

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

图的搜索

  1. 图的搜索指的就是从图的某一顶点开始,
  2. 通过边到达不同的顶点,
  3. 最终找到目标顶点的过程。
  4. 根据搜索的顺序不同,
  5. 图的搜索算法可分为“广度优先搜索”和“深度优先搜索”这两种。
  6. 虽然广度优先搜索和深度优先搜索在搜索顺序上有很大的差异,
  7. 但是在操作步骤上却只有一点不同,
  8. 那就是选择哪一个候补顶点作为下一个顶点的基准不同。
  9. 广度优先搜索选择的是最早成为候补的顶点(FIFO),
  10. 而深度优先搜索选择的则是最新成为候补的顶点(LIFO)。
  11. 摘自 <<我的第一本算法书>> 【日】石田保辉;宫崎修一

目标

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

设计

  • INode: 顶点接口
  • IGraphVisitor: 图的遍历器接口
  • tNode: 顶点的实现
  • iNodeQueue: 候选节点队列. 候选节点的选择方式不同, 决定了是深度优先还是广度优先.
  • tLIFOQueue: LIFO堆栈, 实现iNodeQueue接口
  • tFIFOQeuue: FIFO队列, 实现iNodeQueue接口
  • tGraphVisitor: 遍历器, 实现IGraphVisitor接口,
    • 从指定顶点出发, 访问所有可到达的顶点, 然后返回顶点数组
    • 使用哈希表记录已访问节点, 防止重复访问

单元测试

graph_visit_test.go

  1. package graph
  2. import (
  3. "fmt"
  4. "learning/gooop/graph"
  5. "strings"
  6. "testing"
  7. )
  8. func Test_GraphVisit(t *testing.T) {
  9. fnAssertTrue := func(b bool, msg string) {
  10. if !b {
  11. t.Fatal(msg)
  12. }
  13. }
  14. root := graph.NewNode("ROOT")
  15. a := graph.NewNode("a")
  16. b := graph.NewNode("b")
  17. root.Append(a)
  18. root.Append(b)
  19. ac := graph.NewNode("ac")
  20. ad := graph.NewNode("ad")
  21. a.Append(ac)
  22. a.Append(ad)
  23. be := graph.NewNode("be")
  24. bf := graph.NewNode("bf")
  25. b.Append(be)
  26. b.Append(bf)
  27. acg := graph.NewNode("acg")
  28. ach := graph.NewNode("ach")
  29. ac.Append(acg)
  30. ac.Append(ach)
  31. bfi := graph.NewNode("bfi")
  32. bfj := graph.NewNode("bfj")
  33. bf.Append(bfi)
  34. bf.Append(bfj)
  35. fnVisitGraph := func(policy graph.VisitPolicy) string {
  36. lines := make([]string, 0)
  37. nodes := graph.GraphVisitor.Visit(root, policy)
  38. for _,it := range nodes {
  39. lines = append(lines, fmt.Sprintf("%s", it))
  40. }
  41. return strings.Join(lines, " ")
  42. }
  43. t.Log("testing dfs visitor")
  44. dfs := fnVisitGraph(graph.DFSPolicy)
  45. t.Log(dfs)
  46. 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")
  47. t.Log("testing bfs visitor")
  48. bfs := fnVisitGraph(graph.BFSPolicy)
  49. t.Log(bfs)
  50. 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")
  51. }

测试输出

  1. $ go test -v graph_visit_test.go
  2. === RUN Test_GraphVisit
  3. graph_visit_test.go:53: testing dfs visitor
  4. 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-[]
  5. graph_visit_test.go:58: testing bfs visitor
  6. 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-[]
  7. --- PASS: Test_GraphVisit (0.00s)
  8. PASS
  9. ok command-line-arguments 0.002s

INode.go

顶点接口

  1. package graph
  2. type INode interface {
  3. ID() string
  4. Append(child INode)
  5. Children() []INode
  6. }

IGraphVisitor.go

图的遍历器接口

  1. package graph
  2. type IGraphVisitor interface {
  3. Visit(root INode, policy VisitPolicy) []INode
  4. }
  5. type VisitPolicy int
  6. const DFSPolicy VisitPolicy = 1
  7. const BFSPolicy VisitPolicy = 2

tNode.go

顶点的实现

  1. package graph
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. type tNode struct {
  7. id string
  8. children []INode
  9. }
  10. func NewNode(id string) INode {
  11. return &tNode{
  12. id: id,
  13. children: make([]INode, 0),
  14. }
  15. }
  16. func (me *tNode) ID() string {
  17. return me.id
  18. }
  19. func (me *tNode) Append(child INode) {
  20. me.children = append(me.children, child)
  21. }
  22. func (me *tNode) Children() []INode {
  23. return me.children
  24. }
  25. func (me *tNode) String() string {
  26. items := make([]string, len(me.children))
  27. for i,it := range me.children {
  28. items[i] = it.ID()
  29. }
  30. return fmt.Sprintf("%v-[%s]", me.id, strings.Join(items, ","))
  31. }

iNodeQueue.go

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

  1. package graph
  2. type iNodeQueue interface {
  3. Clear()
  4. Empty() bool
  5. Push(node INode)
  6. Poll() (bool, INode)
  7. }

tLIFOQueue.go

LIFO堆栈, 实现INodeQueue接口

  1. package graph
  2. type tLIFOQueue struct {
  3. nodes []INode
  4. capacity int
  5. size int
  6. }
  7. func newLIFOQueue() iNodeQueue {
  8. it := &tLIFOQueue{}
  9. it.Clear()
  10. return it
  11. }
  12. func (me *tLIFOQueue) Clear() {
  13. me.nodes = make([]INode, 0)
  14. me.capacity = 0
  15. me.size = 0
  16. }
  17. func (me *tLIFOQueue) Push(node INode) {
  18. me.ensureSpace(1)
  19. me.nodes[me.size] = node
  20. me.size++
  21. }
  22. func (me *tLIFOQueue) ensureSpace(space int) {
  23. for me.capacity < me.size + space {
  24. me.nodes = append(me.nodes, nil)
  25. me.capacity++
  26. }
  27. }
  28. func (me *tLIFOQueue) Empty() bool {
  29. return me.size <= 0
  30. }
  31. func (me *tLIFOQueue) Poll() (bool, INode) {
  32. if me.Empty() {
  33. return false, nil
  34. }
  35. me.size--
  36. it := me.nodes[me.size]
  37. me.nodes[me.size] = nil
  38. return true, it
  39. }

tFIFOQeuue.go

FIFO队列, 实现INodeQueue接口

  1. package graph
  2. type tFIFOQueue struct {
  3. nodes []INode
  4. capacity int
  5. rindex int
  6. windex int
  7. }
  8. func newFIFOQueue() iNodeQueue {
  9. it := &tFIFOQueue{}
  10. it.Clear()
  11. return it
  12. }
  13. func (me *tFIFOQueue) Clear() {
  14. me.nodes = make([]INode, 0)
  15. me.capacity = 0
  16. me.rindex = -1
  17. me.windex = -1
  18. }
  19. func (me *tFIFOQueue) size() int {
  20. return me.windex - me.rindex
  21. }
  22. func (me *tFIFOQueue) Empty() bool {
  23. return me.size() <= 0
  24. }
  25. func (me *tFIFOQueue) Push(node INode) {
  26. me.ensureSpace(1)
  27. me.windex++
  28. me.nodes[me.windex] = node
  29. }
  30. func (me *tFIFOQueue) ensureSpace(size int) {
  31. for me.capacity < me.windex + size + 1 {
  32. me.nodes = append(me.nodes, nil)
  33. me.capacity++
  34. }
  35. }
  36. func (me *tFIFOQueue) Poll() (bool, INode) {
  37. if me.Empty() {
  38. return false, nil
  39. }
  40. me.rindex++
  41. it := me.nodes[me.rindex]
  42. me.nodes[me.rindex] = nil
  43. if me.rindex > me.capacity / 2 {
  44. size := me.size()
  45. offset := me.rindex + 1
  46. for i := 0;i < size;i++ {
  47. me.nodes[i], me.nodes[i + offset] = me.nodes[i + offset], nil
  48. }
  49. me.rindex -= offset
  50. me.windex -= offset
  51. }
  52. return true, it
  53. }

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

  1. case DFSPolicy:
  2. return newLIFOQueue()
  3. default:
  4. panic("unsupported policy")
  5. }

}

func (me *tGraphVisitor) Visit(root INode, policy VisitPolicy) []INode { queue := me.createQueue(policy) queue.Push(root)

  1. visited := make(map[string]bool, 0)
  2. result := make([]INode, 0)
  3. for !queue.Empty() {
  4. _,node := queue.Poll()
  5. visited[node.ID()] = true
  6. result = append(result, node)
  7. children := node.Children()
  8. if children != nil {
  9. for _,it := range children {
  10. ok,_ := visited[it.ID()]
  11. if ok {
  12. continue
  13. }
  14. queue.Push(it)
  15. }
  16. }
  17. }
  18. return result

}

var GraphVisitor = newGraphVisitor() ```

(end)