介绍

广度优先搜索算法(Breadth-First Search,BFS)会以相关联的节点展开的方式,系统性的遍历所有的节点,直到找到目标节点为止。

广度优先搜索是一种盲目的搜索算法,但是仍能找出两点间最短的路径。

搜索流程

image.png
有图如上所示,图的顶点 A B C D E F G。现在假设以顶点 A 作为起点,顶点 D 作为终点, 算法的搜索步骤如下:

  1. 起点 A 扩展出两个邻接的顶点,分别是顶点 B 和顶点 G。标记已扩展节点。在扩展出的顶点里,没有目标顶点;
  2. 继续扩展刚扩展出的顶点。
    1. 顶点 B 扩展出邻接的顶点 C 和顶点 F
    2. 顶点 G 扩展出邻接的顶点 E 和顶点 F
    3. 顶点 F 会在第一次被扩展后标记为已扩展,所以 B 或者 G 理论上只有一个顶点能扩展到 F,由扩展顺序决定;
    4. 在扩展出的顶点里,仍没有目标顶点;
  3. 此时,顶点 F 没有未扩展节点,顶点 C 和顶点 E 能扩展到顶点 D,扩展得到顶点 D 就是此次搜索的目标顶点。得到路径 A -> B -> C -> DA -> G -> E -> D

image.png

队列

我们要遍历的是与顶点邻接的顶点,要求遍历完当前顶点的邻接顶点后马上就遍历邻接顶点的新的邻接顶点。因此,队列(Queue)是最符合当前运用场景的。

队列是一种特殊的线性表。队列的操作方式为从队列尾部插入,从队列的头部弹出。
image.png
可以使用 C++ STL 库提供的 queue 实现。

伪代码

以下伪代码的实现遍历图上所有的顶点。

  1. 广度优先搜索算法(起点v)
  2. {
  3. 标记起点v为已遍历;
  4. 初始化队列Q;
  5. 将起点v插入到队列里;
  6. while(Q非空)
  7. {
  8. 从队列中弹出顶点w;
  9. 获取顶点w的邻接顶点u;
  10. while(u != NULL)
  11. {
  12. if (顶点u未被遍历)
  13. {
  14. 添加u到队列中;
  15. 标记u已遍历;
  16. }
  17. 获取顶点w的另一个邻接顶点u;
  18. }
  19. }
  20. }

以下稍作修改,利用广度优先搜索算法寻找终点。

  1. 广度优先搜索算法(起点v, 终点u)
  2. {
  3. 标记起点v为已遍历;
  4. 初始化队列Q;
  5. 将起点v插入到队列里;
  6. while(Q非空)
  7. {
  8. 从队列中弹出顶点w;
  9. 获取顶点w的邻接顶点n;
  10. 设置n的父节点为w
  11. if (顶点n 终点u)
  12. {
  13. 结束获取,返回终点;
  14. }
  15. while(n != NULL)
  16. {
  17. if (顶点n未被遍历)
  18. {
  19. 添加n到队列中;
  20. 标记n已遍历;
  21. }
  22. 获取顶点w的另一个邻接顶点n;
  23. }
  24. }
  25. }
  26. 获取路径(起点v, 终点u)
  27. {
  28. 定义并初始化一个队列Q
  29. 将终点u插入到队列里;
  30. 将终点u设置为当前顶点;
  31. while(当前顶点的父顶点 != nullptr)
  32. {
  33. 将当前顶点的父顶点插入到队列里;
  34. 设置父顶点为当前的顶点;
  35. }
  36. 返回队列Q为当前的最佳路径;
  37. }

代码

以一个 10 x 10 的栅格地图为例。

  1. #include <iostream>
  2. #include <queue>
  3. #include <cstring>
  4. /* 0:不可通行 */
  5. /* 1:可通行 */
  6. uint8_t map[10][10]
  7. {
  8. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  9. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  10. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  11. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  12. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  13. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  14. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  15. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  16. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  17. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  18. };
  19. struct Vertex
  20. {
  21. uint8_t x, y;
  22. uint16_t pre;
  23. bool reached;
  24. };
  25. uint8_t * closeMap = nullptr;
  26. bool breadthFirstSearch(Vertex start, Vertex end)
  27. {
  28. std::queue<Vertex> mList;
  29. mList.push(start);
  30. int index = start.y * 10 + start.x;
  31. closeMap[index] = 1;
  32. while(mList.empty() == false)
  33. {
  34. Vertex w = mList.front();
  35. mList.pop();
  36. for (int i = -1; i <= 1; i ++)
  37. {
  38. int ii = w.x + i;
  39. for (int j = -1; j <= 1; j ++)
  40. {
  41. int jj = w.y + j;
  42. int ww = jj * 10 + ii;
  43. if (ii >= 0 && jj >= 0 && ii < 10 && jj < 10)
  44. {
  45. if (map[ii][jj] == 1)
  46. {
  47. if (closeMap != nullptr)
  48. {
  49. if (closeMap[ww] == 0)
  50. {
  51. Vertex n;
  52. n.x = ii;
  53. n.y = jj;
  54. n.pre = (uint16_t)((ii << 4) | (jj));
  55. mList.push(n);
  56. closeMap[ww] = 1;
  57. }
  58. }
  59. }
  60. if (ii == end.x && jj == end.y)
  61. {
  62. return true;
  63. }
  64. }
  65. }
  66. }
  67. }
  68. return false;
  69. }
  70. int main(int argc, char * argv[])
  71. {
  72. printf("hello world\r\n");
  73. closeMap = new uint8_t[100];
  74. memset(closeMap, 0, sizeof(uint8_t) * 100);
  75. Vertex start_p;
  76. start_p.x = 1;
  77. start_p.y = 1;
  78. Vertex end_p;
  79. end_p.x = 8;
  80. end_p.y = 8;
  81. if (breadthFirstSearch(start_p, end_p))
  82. {
  83. printf("bfs success\r\n");
  84. }
  85. else
  86. {
  87. printf("bfs failed\r\n");
  88. }
  89. delete [] closeMap;
  90. return 1;
  91. }