缘起

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

贝尔曼-福特算法

  1. 贝尔曼-福特(Bellman-Ford)算法是一种在图中求解最短路径问题的算法。
  2. 最短路径问题就是在加权图指定了起点和终点的前提下,
  3. 寻找从起点到终点的路径中权重总和最小的那条路径。
  4. 摘自 <<我的第一本算法书>> 【日】石田保辉;宫崎修一

流程

  1. 给定若干顶点, 以及顶点间的若干条边, 寻找从指定起点from到指定终点to的最小权重路径
  2. 设定from的权重为0, 其他顶点的权重为无穷大
  3. 将from节点送入候选队列
  4. for 候选队列不为空:
    1. 从候选队列出队一个顶点node
    2. 遍历从node出发的所有边, 将边的终点权重, 更新为min(终点权重, node.权重+边.权重)
    3. 如果终点权重 > node.权重+边.权重, 说明更新有效, 则将终点push到候选队列
  5. 判断终点的权重是否被更新(!=无穷大), 如果是则说明存在最短路径
  6. 反向查找最短路径:
    1. 设定当前节点current = 终点
    2. push节点current进路径队列
    3. 遍历终点为current的边, 查找符合条件的node:边的起点.权重 = current.权重-边.权重
    4. push节点node进路径队列
    5. 循环1-4, 直到current == from, 查找完成

目标

  • 实现并验证贝尔曼-福特算法

设计

  • INode: 顶点接口
  • ILine: 边接口
  • IPathFinder: 最短路径查找算法接口
  • iNodeQueue: 顶点队列接口, FIFO队列
  • tNode: 顶点, 实现INode
  • tLine: 边, 实现ILine
  • tFIFOQueue: FIFO节点队列的实现
  • tBellmanFoldFinder: 贝尔曼-福特算法的实现

单元测试

bellman_fold_test.go

  1. package graph
  2. import (
  3. "fmt"
  4. bf "learning/gooop/graph/bellman_fold"
  5. "strings"
  6. "testing"
  7. )
  8. func Test_BellmanFold(t *testing.T) {
  9. fnAssertTrue := func(b bool, msg string) {
  10. if !b {
  11. t.Fatal(msg)
  12. }
  13. }
  14. nodes := []bf.INode{
  15. bf.NewNode("a"),
  16. bf.NewNode("b"),
  17. bf.NewNode("c"),
  18. bf.NewNode("d"),
  19. bf.NewNode("e"),
  20. bf.NewNode("f"),
  21. bf.NewNode("g"),
  22. }
  23. lines := []bf.ILine {
  24. bf.NewLine("a", "b", 9),
  25. bf.NewLine("a", "c", 2),
  26. bf.NewLine("b", "c", 6),
  27. bf.NewLine("b", "d", 3),
  28. bf.NewLine("b", "e", 1),
  29. bf.NewLine("c", "d", 2),
  30. bf.NewLine("c", "f", 9),
  31. bf.NewLine("d", "e", 5),
  32. bf.NewLine("d", "f", 6),
  33. bf.NewLine("e", "f", 3),
  34. bf.NewLine("e", "g", 7),
  35. bf.NewLine("f", "g", 4),
  36. }
  37. for _,it := range lines[:] {
  38. lines = append(lines, bf.NewLine(it.To(), it.From(), it.Weight()))
  39. }
  40. ok,path := bf.BellmanFoldFinder.FindPath(nodes, lines, "a", "g")
  41. if !ok {
  42. t.Fatal("failed to find min path")
  43. }
  44. fnPathToString := func(nodes []bf.INode) string {
  45. items := make([]string, len(nodes))
  46. for i,it := range nodes {
  47. items[i] = fmt.Sprintf("%s", it)
  48. }
  49. return strings.Join(items, " ")
  50. }
  51. pathString := fnPathToString(path)
  52. fnAssertTrue(pathString == "a(0) c(2) d(4) f(10) g(14)", "incorrect path")
  53. }

测试输出

  1. $ go test -v bellman_fold_test.go
  2. === RUN Test_BellmanFold
  3. bellman_fold_test.go:63: a(0) c(2) d(4) f(10) g(14)
  4. --- PASS: Test_BellmanFold (0.00s)
  5. PASS
  6. ok command-line-arguments 0.002s

INode.go

顶点接口

  1. package bellman_fold
  2. type INode interface {
  3. ID() string
  4. GetWeight() int
  5. SetWeight(int)
  6. }
  7. const MaxWeight = int(0x7fffffff_ffffffff)

ILine.go

边接口

  1. package bellman_fold
  2. type ILine interface {
  3. From() string
  4. To() string
  5. Weight() int
  6. }

IPathFinder.go

最短路径查找算法接口

  1. package bellman_fold
  2. type IPathFinder interface {
  3. FindPath(nodes []INode, lines []ILine, from string, to string) (bool,[]INode)
  4. }

iNodeQueue.go

顶点队列接口, FIFO队列

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

tNode.go

顶点, 实现INode

  1. package bellman_fold
  2. import "fmt"
  3. type tNode struct {
  4. id string
  5. weight int
  6. }
  7. func NewNode(id string) INode {
  8. return &tNode{
  9. id,MaxWeight,
  10. }
  11. }
  12. func (me *tNode) ID() string {
  13. return me.id
  14. }
  15. func (me *tNode) GetWeight() int {
  16. return me.weight
  17. }
  18. func (me *tNode) SetWeight(w int) {
  19. me.weight = w
  20. }
  21. func (me *tNode) String() string {
  22. return fmt.Sprintf("%s(%v)", me.id, me.weight)
  23. }

tLine.go

边, 实现ILine

  1. package bellman_fold
  2. type tLine struct {
  3. from string
  4. to string
  5. weight int
  6. }
  7. func NewLine(from string, to string, weight int) ILine {
  8. return &tLine{
  9. from,to,weight,
  10. }
  11. }
  12. func (me *tLine) From() string {
  13. return me.from
  14. }
  15. func (me *tLine) To() string {
  16. return me.to
  17. }
  18. func (me *tLine) Weight() int {
  19. return me.weight
  20. }

tFIFOQueue.go

FIFO节点队列的实现

  1. package bellman_fold
  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. }

tBellmanFoldFinder.go

贝尔曼-福特算法的实现

  1. package bellman_fold
  2. type tBellmanFoldFinder struct {
  3. }
  4. func newBellmanFoldFinder() IPathFinder {
  5. return &tBellmanFoldFinder{
  6. }
  7. }
  8. func (me *tBellmanFoldFinder) FindPath(nodes []INode, lines []ILine, fromID string, toID string) (bool,[]INode) {
  9. // 节点索引
  10. mapNodes := make(map[string]INode, 0)
  11. for _,it := range nodes {
  12. mapNodes[it.ID()] = it
  13. }
  14. fromNode, ok := mapNodes[fromID]
  15. if !ok {
  16. return false, nil
  17. }
  18. toNode,ok := mapNodes[toID]
  19. if !ok {
  20. return false, nil
  21. }
  22. // 边的索引
  23. mapFromLines := make(map[string][]ILine, 0)
  24. mapToLines := make(map[string][]ILine, 0)
  25. for _, it := range lines {
  26. if v,ok := mapFromLines[it.From()];ok {
  27. mapFromLines[it.From()] = append(v, it)
  28. } else {
  29. mapFromLines[it.From()] = []ILine{ it }
  30. }
  31. if v,ok := mapToLines[it.To()];ok {
  32. mapToLines[it.To()] = append(v, it)
  33. } else {
  34. mapToLines[it.To()] = []ILine{ it }
  35. }
  36. }
  37. // 设置from节点的weight为0, 其他节点的weight为MaxWeight
  38. for _,it := range nodes {
  39. if it.ID() == fromID {
  40. it.SetWeight(0)
  41. } else {
  42. it.SetWeight(MaxWeight)
  43. }
  44. }
  45. // 循环更新所有节点的权重 直到不再变化
  46. fromNode.SetWeight(0)
  47. queue := newFIFOQueue()
  48. queue.Push(fromNode)
  49. for !queue.Empty() {
  50. ok,from := queue.Poll()
  51. if !ok {
  52. panic("unexpected !ok")
  53. }
  54. affectedLines, ok := mapFromLines[from.ID()]
  55. if ok {
  56. for _,line := range affectedLines {
  57. if to,ok := mapNodes[line.To()];ok {
  58. if me.updateWeight(from, to, line) {
  59. queue.Push(to)
  60. }
  61. }
  62. }
  63. }
  64. }
  65. // 逆向查找最短路径
  66. if toNode.GetWeight() >= MaxWeight {
  67. return false, nil
  68. }
  69. queue.Clear()
  70. queue.Push(toNode)
  71. current := toNode
  72. maxRound := len(lines)
  73. for ;current != fromNode && maxRound > 0;maxRound-- {
  74. linkedLines, _ := mapToLines[current.ID()]
  75. for _,line := range linkedLines {
  76. from, _ := mapNodes[line.From()]
  77. if from.GetWeight() == current.GetWeight() - line.Weight() {
  78. current = from
  79. queue.Push(from)
  80. }
  81. }
  82. }
  83. if current != fromNode {
  84. return false, nil
  85. }
  86. // 返回
  87. result := make([]INode, queue.Size())
  88. for i := queue.Size() - 1;i >= 0;i-- {
  89. _,result[i] = queue.Poll()
  90. }
  91. return true, result
  92. }
  93. func (me *tBellmanFoldFinder) updateWeight(from INode, to INode, line ILine) bool {
  94. w := me.min(from.GetWeight() + line.Weight(), to.GetWeight())
  95. if to.GetWeight() > w {
  96. to.SetWeight(w)
  97. return true
  98. }
  99. return false
  100. }
  101. func (me *tBellmanFoldFinder) min(a, b int) int {
  102. if a <= b {
  103. return a
  104. }
  105. return b
  106. }
  107. var BellmanFoldFinder = newBellmanFoldFinder()

(end)