[关闭]
@Dmaxiya 2020-08-25T00:40:07.000000Z 字数 11537 阅读 1344

广度优先搜索

Hello_World


bfs

广度优先搜索 简称 ,它的搜法是对于当前状态,依次搜索其后继状态,其后继状态都搜索结束后,再搜索这些后继状态所产生的更多的后继状态,从搜索树的角度来看,就是搜索同一深度的所有状态,再搜索下一深度的所有状态。
对于下面这棵树,宽搜的顺序就是按 的字母表顺序。

下面这张图从 点开始的宽搜顺序为:


中括号的节点表示搜索到当前节点的前一节点编号,小括号中的节点表示本应该搜索到当前节点,但由于之前已经搜索过,所以应当跳过搜索。

bfs 思路

从上面的搜索次序可以大概这样理解图上 :从某个点开始一层一层地往外搜索节点,直到所有节点都被搜索到。在上图的搜索过程中,搜索到节点 时,不再继续往下搜索,而是先存起来,等到 搜索结束后再开始 的搜索。
看到这里我们可以用一个二维数组 表示从初始节点开始,搜索到第 层时的第 个节点(把“层”这个字放在树上会不会更好理解一些?),在搜索到第 层时,在第 层的一维数组上跑一个 循环即可。但是二维数组的需要的空间太大,如果真的要用二维数组来实现宽搜,空间复杂度为 ,其中 为节点数。
进一步观察我们可以发现,第 层的搜索次序只依赖于第 层的搜索,而完全用不到第 层之前的搜索数据,所以我们可以将这么大的一个二维数组用一个滚动数组代替,即将第 层的搜索结果储存在第 行的一维数组中,而第 层的搜索就可以通过对第 行的一维数组进行一次 循环。这样的空间复杂度就可以下降到 ,已经是足够优化的空间了,只是实现起来由于宽搜搜索层数的不确定,不好给出“跳出循环”或者“结束递归” 的条件(取决于你用哪种方法实现)。

继续观察这个滚动数组 循环的次序可以发现,这个次序总是按顺序在两个数组中从左到右依次扫描,对于第 行的一维数组,当我们扫描到第 个节点时,会将第 个节点的所有没有被访问过的后继节点都放入到第 行一维数组的末尾,接着第 个节点就不会被扫描到,而是继续扫描第 个节点,直到第 行的所有节点都被扫描到,进而继续扫描第 行的节点。
我们能否将这两个一维数组拼接起来,仅用一维来实现呢?以上的扫描次序和添加待扫描节点的次序正好符合“先进先出”的规律,所以这一维数组也就可以转化为一个队列,用一个队列来存放待扫描的所有节点,如果队首的节点所有后继节点已经被存入队列,则这个节点就已经失去了作用,可以从队列中剔除,并且不再需要它。

将本小结“节点”的含义替换成“状态”,将“图”替换成“解空间”,同样成立。

bfs 代码实现

  1. queue<Node> que;
  2. bool vis[maxn];
  3. que.push(s);
  4. vis[s] = true;
  5. while(!que.empty()) {
  6. Node tmp = que.front();
  7. que.pop();
  8. /*对 tmp 节点的相关操作*/
  9. int len = G[tmp.pos].size();
  10. for(int i = 0; i < len; ++i) {
  11. int pos = G[tmp.pos][i].pos;
  12. if(!vis[pos] && (/*其他能够进入队列的条件*/)) {
  13. /*对 pos 节点的相关操作*/
  14. vis[pos] = true;
  15. que.push(pos);
  16. }
  17. }
  18. }

如果由于某些条件必须跳出循环,则可以在第 行加入一个判断语句然后 ,并且需要在第 行加入清空队列的初始化:

  1. while(!que.empty()) {
  2. que.pop();
  3. }

关于队列的相关函数的作用和意义,请下载群文件“ 提交 基础”自学 相关内容,假期不再布置 相关习题。

bfs 应用

  1. 边权相等情况下搜索从一个点到达图上其他所有点的最短路径;由于边权相同,所以到达 节点的时间等于到达 节点的宽搜层数,第一次搜索到节点 的层数必然是最小层数,所以第一次搜索到节点 的路径就是到达节点 的最短路径。
  2. 拓扑排序;拓扑排序的宽搜写法比较容易实现与理解(还有深搜的写法),但是注意拓扑排序初始进入队列的不是一个点,而是所有入度为零的点。
  3. 迪杰斯特拉最短路;迪杰斯特拉最短路算法的堆优化(将队列改为优先队列)写法时间复杂度为 ,其中 为图上边的数量,对于完全图( 条边)的时间复杂度为 ,效率不如 的朴素写法()。
  4. 最小生成树; 最小生成树算法的堆优化时间复杂度为 ,完全图的情况和 相同,此时最好直接用 的朴素写法
  5. 序:目前看到用到 序的好像只有“ 自动机”算法,这个是 字典树上跑 的一种多字符串匹配的算法,若是涉及到 自动机的话比较常用,蓝桥杯一般不考,可以不用理解。

2 月 13~14 日 bfs 练习题解

A. Knight Moves

题意

在一个 的方格中,走“日”字步从 点走到 点,问最少走多少步。

数据范围

题解

点开始 ,第一次搜索到达的点用的步数一定是最少的,所以只要记录第一次到达每个点的步数,后面若重复搜索到这个点,则不再进入队列,最后输出到达 的步数。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cfloat>
  7. #include <cstring>
  8. #include <string>
  9. #include <sstream>
  10. #include <vector>
  11. #include <list>
  12. #include <queue>
  13. #include <stack>
  14. #include <map>
  15. #include <set>
  16. #include <bitset>
  17. #include <algorithm>
  18. #include <ctime>
  19. #include <functional>
  20. #include <iomanip>
  21. using namespace std;
  22. #define LL long long
  23. const int maxn = 500;
  24. struct Node {
  25. int x, y;
  26. Node() {}
  27. Node(int xx, int yy) {
  28. x = xx;
  29. y = yy;
  30. }
  31. };
  32. bool operator==(const Node &a, const Node &b) {
  33. return a.x == b.x && a.y == b.y;
  34. }
  35. int T, n;
  36. Node s, t;
  37. int step[maxn][maxn];
  38. queue<Node> que;
  39. const int dir[8][2] = {{-1, -2}, {-2, -1}, {-2, 1}, {-1, 2}, {1, -2}, {2, -1}, {1, 2}, {2, 1}};
  40. bool in(int x, int y) {
  41. return x >= 0 && x < n && y >= 0 && y < n;
  42. }
  43. int bfs() {
  44. while(!que.empty()) {
  45. que.pop();
  46. }
  47. memset(step, -1, sizeof(step));
  48. que.push(s);
  49. step[s.x][s.y] = 0;
  50. while(!que.empty()) {
  51. Node tmp = que.front();
  52. que.pop();
  53. if(tmp == t) {
  54. break;
  55. }
  56. for(int i = 0; i < 8; ++i) {
  57. int xx = tmp.x + dir[i][0];
  58. int yy = tmp.y + dir[i][1];
  59. if(in(xx, yy) && step[xx][yy] == -1) {
  60. step[xx][yy] = step[tmp.x][tmp.y] + 1;
  61. que.push(Node(xx, yy));
  62. }
  63. }
  64. }
  65. return step[t.x][t.y];
  66. }
  67. int main() {
  68. #ifdef LOCAL
  69. freopen("test.txt", "r", stdin);
  70. // freopen("out.txt", "w", stdout);
  71. #endif // LOCAL
  72. ios::sync_with_stdio(false);
  73. scanf("%d", &T);
  74. while(T--) {
  75. scanf("%d", &n);
  76. scanf("%d%d%d%d", &s.x, &s.y, &t.x, &t.y);
  77. printf("%d\n", bfs());
  78. }
  79. return 0;
  80. }

B. Catch That Cow

题意

在一个一维坐标轴上, 最初在 点,他的牛跑到了 点,牛在 点不动,若他在点 ,则他下一步可以移动到 这三个位置,问他最快多久能找到牛。

数据范围

题解

点开始 ,思路同上,但是注意农夫可以走的范围实际上是 ,所以要注意数组的大小,否则很可能会

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cfloat>
  7. #include <cstring>
  8. #include <string>
  9. #include <sstream>
  10. #include <vector>
  11. #include <list>
  12. #include <queue>
  13. #include <stack>
  14. #include <map>
  15. #include <set>
  16. #include <bitset>
  17. #include <algorithm>
  18. #include <ctime>
  19. #include <functional>
  20. #include <iomanip>
  21. using namespace std;
  22. #define LL long long
  23. const int maxn = 200000 + 100;
  24. int n, k;
  25. int step[maxn];
  26. int Set[maxn], cnt;
  27. queue<int> que;
  28. void Change(int Index, int x) {
  29. Set[cnt++] = Index;
  30. step[Index] = x;
  31. }
  32. void Clear() {
  33. for(int i = 0; i < cnt; ++i) {
  34. step[Set[i]] = -1;
  35. }
  36. cnt = 0;
  37. }
  38. bool in(int x) {
  39. return x >= 0 && x < maxn;
  40. }
  41. int bfs() {
  42. while(!que.empty()) {
  43. que.pop();
  44. }
  45. que.push(n);
  46. Change(n, 0);
  47. while(!que.empty()) {
  48. int tmp = que.front();
  49. que.pop();
  50. if(tmp == k) {
  51. break;
  52. }
  53. if(in(tmp - 1) && step[tmp - 1] == -1) {
  54. Change(tmp - 1, step[tmp] + 1);
  55. que.push(tmp - 1);
  56. }
  57. if(in(tmp + 1) && step[tmp + 1] == -1) {
  58. Change(tmp + 1, step[tmp] + 1);
  59. que.push(tmp + 1);
  60. }
  61. if(in(tmp * 2) && step[tmp * 2] == -1) {
  62. Change(tmp * 2, step[tmp] + 1);
  63. que.push(tmp * 2);
  64. }
  65. }
  66. return step[k];
  67. }
  68. int main() {
  69. #ifdef LOCAL
  70. freopen("test.txt", "r", stdin);
  71. // freopen("out.txt", "w", stdout);
  72. #endif // LOCAL
  73. ios::sync_with_stdio(false);
  74. memset(step, -1, sizeof(step));
  75. while(scanf("%d%d", &n, &k) != EOF) {
  76. Clear();
  77. printf("%d\n", bfs());
  78. }
  79. return 0;
  80. }

C. Dating with girls(2)

题意

你想和一个女生约会,你和这个女生都在一个 的迷宫当中,这个迷宫中的石头将会在 的时间点消失,其他时候它们都将在那里。要从 的位置走到 的位置,问最少需要多少分钟。

数据范围

题解

石头会在 的整数倍的时间点消失,所以整个搜索的周期不是 而是 ,如果周期为 就是前面两道题的宽搜,周期为 就多开一维表示某个周期中的第 个时间点到达某个点的最短时间。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cfloat>
  7. #include <cstring>
  8. #include <string>
  9. #include <sstream>
  10. #include <vector>
  11. #include <list>
  12. #include <queue>
  13. #include <stack>
  14. #include <map>
  15. #include <set>
  16. #include <bitset>
  17. #include <algorithm>
  18. #include <ctime>
  19. #include <functional>
  20. #include <iomanip>
  21. using namespace std;
  22. #define LL long long
  23. const int maxn = 200;
  24. struct Node {
  25. int x, y;
  26. int k;
  27. Node() {}
  28. Node(int xx, int yy, int kk) {
  29. x = xx;
  30. y = yy;
  31. k = kk;
  32. }
  33. };
  34. bool operator==(const Node &a, const Node &b) {
  35. return a.x == b.x && a.y == b.y;
  36. }
  37. int T, n, m, k;
  38. Node s, t;
  39. queue<Node> que;
  40. char str[maxn][maxn];
  41. int step[maxn][maxn][15];
  42. const int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
  43. bool in(int x, int y) {
  44. return x >= 0 && x < n && y >= 0 && y < m;
  45. }
  46. int bfs() {
  47. while(!que.empty()) {
  48. que.pop();
  49. }
  50. memset(step, -1, sizeof(step));
  51. step[s.x][s.y][s.k] = 0;
  52. que.push(s);
  53. while(!que.empty()) {
  54. Node tmp = que.front();
  55. que.pop();
  56. if(tmp == t) {
  57. return step[tmp.x][tmp.y][tmp.k];
  58. }
  59. int St = step[tmp.x][tmp.y][tmp.k] + 1;
  60. for(int i = 0; i < 4; ++i) {
  61. int xx = tmp.x + dir[i][0];
  62. int yy = tmp.y + dir[i][1];
  63. if(in(xx, yy) && step[xx][yy][St % k] == -1 && (St % k == 0 || str[xx][yy] != '#')) {
  64. step[xx][yy][St % k] = St;
  65. que.push(Node(xx, yy, St % k));
  66. }
  67. }
  68. }
  69. return -1;
  70. }
  71. int main() {
  72. #ifdef LOCAL
  73. freopen("test.txt", "r", stdin);
  74. // freopen("out.txt", "w", stdout);
  75. #endif // LOCAL
  76. ios::sync_with_stdio(false);
  77. scanf("%d", &T);
  78. while(T--) {
  79. scanf("%d%d%d", &n, &m, &k);
  80. for(int i = 0; i < n; ++i) {
  81. scanf("%s", str[i]);
  82. for(int j = 0; j < m; ++j) {
  83. if(str[i][j] == 'Y') {
  84. s.x = i;
  85. s.y = j;
  86. s.k = 0;
  87. } else if(str[i][j] == 'G') {
  88. t.x = i;
  89. t.y = j;
  90. }
  91. }
  92. }
  93. int ans = bfs();
  94. if(ans == -1) {
  95. printf("Please give me another chance!\n");
  96. } else {
  97. printf("%d\n", ans);
  98. }
  99. }
  100. return 0;
  101. }

2月15日 bfs 练习题解

A. Nightmare

题意

在一个 的迷宫中, 手里拿着一个定时炸弹,最初倒计时设为 分钟,他可以在迷宫中上下左右移动,每移动一格耗时 分钟,在迷宫的某些地方设有重置器,可以将定时炸弹的时间重置为 分钟。如果定时器倒计时到 ,不论走到哪一格,炸弹都将爆炸,问他能否安全到达出口,如果可以,最短需要多长时间。

数据范围

题解

Dating with girls(2) 题一样,多开一维表示炸弹倒计时为 时,到达某一格的最短时间,到达某一格时,若倒计时为 且这一格不是出口,则不再搜索。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cfloat>
  7. #include <cstring>
  8. #include <string>
  9. #include <sstream>
  10. #include <vector>
  11. #include <list>
  12. #include <queue>
  13. #include <stack>
  14. #include <map>
  15. #include <set>
  16. #include <bitset>
  17. #include <algorithm>
  18. #include <ctime>
  19. #include <functional>
  20. #include <iomanip>
  21. using namespace std;
  22. #define LL long long
  23. const int maxn = 20;
  24. struct Node {
  25. int x, y;
  26. int Time;
  27. Node() {}
  28. Node(int xx, int yy, int t) {
  29. x = xx;
  30. y = yy;
  31. Time = t;
  32. }
  33. };
  34. bool operator==(const Node &a, const Node &b) {
  35. return a.x == b.x && a.y == b.y;
  36. }
  37. int T, n, m;
  38. Node s, t;
  39. queue<Node> que;
  40. int step[maxn][maxn][10];
  41. int num[maxn][maxn];
  42. const int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
  43. bool in(int x, int y) {
  44. return x >= 0 && x < n && y >= 0 && y < m;
  45. }
  46. int bfs() {
  47. while(!que.empty()) {
  48. que.pop();
  49. }
  50. memset(step, -1, sizeof(step));
  51. step[s.x][s.y][s.Time] = 0;
  52. que.push(s);
  53. while(!que.empty()) {
  54. Node tmp = que.front();
  55. que.pop();
  56. if(tmp == t) {
  57. return step[tmp.x][tmp.y][tmp.Time];
  58. }
  59. if(tmp.Time == 1) {
  60. continue;
  61. }
  62. for(int i = 0; i < 4; ++i) {
  63. int xx = tmp.x + dir[i][0];
  64. int yy = tmp.y + dir[i][1];
  65. int Time = tmp.Time - 1;
  66. if(in(xx, yy) && num[xx][yy] != 0) {
  67. if(num[xx][yy] == 4) {
  68. Time = 6;
  69. }
  70. if(step[xx][yy][Time] == -1) {
  71. step[xx][yy][Time] = step[tmp.x][tmp.y][tmp.Time] + 1;
  72. que.push(Node(xx, yy, Time));
  73. }
  74. }
  75. }
  76. }
  77. return -1;
  78. }
  79. int main() {
  80. #ifdef LOCAL
  81. freopen("test.txt", "r", stdin);
  82. // freopen("out.txt", "w", stdout);
  83. #endif // LOCAL
  84. ios::sync_with_stdio(false);
  85. scanf("%d", &T);
  86. while(T--) {
  87. scanf("%d%d", &n, &m);
  88. for(int i = 0; i < n; ++i) {
  89. for(int j = 0; j < m; ++j) {
  90. scanf("%d", &num[i][j]);
  91. if(num[i][j] == 2) {
  92. s.x = i;
  93. s.y = j;
  94. s.Time = 6;
  95. } else if(num[i][j] == 3) {
  96. t.x = i;
  97. t.y = j;
  98. }
  99. }
  100. }
  101. printf("%d\n", bfs());
  102. }
  103. return 0;
  104. }

B. 诡异的楼梯

题解

到达楼梯时判可以通过判断奇偶性来判断能否通过这个楼梯,如果不能通过且过楼梯后的那一个没有被搜索到,则在原地等一分钟再过楼梯。这里注意一下过从楼梯一边走到另一边只需要一分钟。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cfloat>
  7. #include <cstring>
  8. #include <string>
  9. #include <sstream>
  10. #include <vector>
  11. #include <list>
  12. #include <queue>
  13. #include <stack>
  14. #include <map>
  15. #include <set>
  16. #include <bitset>
  17. #include <algorithm>
  18. #include <ctime>
  19. #include <functional>
  20. #include <iomanip>
  21. using namespace std;
  22. #define LL long long
  23. const int maxn = 30;
  24. struct Node {
  25. int x, y;
  26. int step;
  27. Node() {}
  28. Node(int xx, int yy, int s) {
  29. x = xx;
  30. y = yy;
  31. step = s;
  32. }
  33. };
  34. bool operator==(const Node &a, const Node &b) {
  35. return a.x == b.x && a.y == b.y;
  36. }
  37. int n, m;
  38. Node s, t;
  39. queue<Node> que;
  40. char G[maxn][maxn];
  41. const int dir[4][2] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
  42. bool vis[maxn][maxn];
  43. bool in(int x, int y) {
  44. return x >= 0 && x < n && y >= 0 && y < m;
  45. }
  46. int bfs() {
  47. int ret;
  48. while(!que.empty()) {
  49. que.pop();
  50. }
  51. memset(vis, 0, sizeof(vis));
  52. que.push(s);
  53. vis[s.x][s.y] = true;
  54. while(!que.empty()) {
  55. Node tmp = que.front();
  56. que.pop();
  57. if(tmp == t) {
  58. ret = tmp.step;
  59. break;
  60. }
  61. for(int i = 0; i < 4; ++i) {
  62. int xx = tmp.x + dir[i][0];
  63. int yy = tmp.y + dir[i][1];
  64. if(in(xx, yy)) {
  65. if(G[xx][yy] == 0 || G[xx][yy] == 1) {
  66. int tmpx = xx + dir[i][0];
  67. int tmpy = yy + dir[i][1];
  68. if(in(tmpx, tmpy) && !vis[tmpx][tmpy]) {
  69. if(((tmp.step & 1) ^ G[xx][yy] ^ (i & 1)) == 1) {
  70. que.push(Node(tmp.x, tmp.y, tmp.step + 1));
  71. } else {
  72. vis[tmpx][tmpy] = true;
  73. que.push(Node(tmpx, tmpy, tmp.step + 1));
  74. }
  75. }
  76. } else if(!vis[xx][yy] && G[xx][yy] != '*') {
  77. vis[xx][yy] = true;
  78. que.push(Node(xx, yy, tmp.step + 1));
  79. }
  80. }
  81. }
  82. }
  83. return ret;
  84. }
  85. int main() {
  86. #ifdef LOCAL
  87. freopen("test.txt", "r", stdin);
  88. // freopen("out.txt", "w", stdout);
  89. #endif // LOCAL
  90. ios::sync_with_stdio(false);
  91. while(scanf("%d%d", &n, &m) != EOF) {
  92. for(int i = 0; i < n; ++i) {
  93. scanf("%s", G[i]);
  94. for(int j = 0; j < m; ++j) {
  95. if(G[i][j] == 'S') {
  96. s.x = i;
  97. s.y = j;
  98. s.step = 0;
  99. } else if(G[i][j] == 'T') {
  100. t.x = i;
  101. t.y = j;
  102. } else if(G[i][j] == '|') {
  103. G[i][j] = 0;
  104. } else if(G[i][j] == '-') {
  105. G[i][j] = 1;
  106. }
  107. }
  108. }
  109. printf("%d\n", bfs());
  110. }
  111. return 0;
  112. }

B. 变形课

题解

如果一个单词的首字母为 ,最后一个字母为 ,则在一个邻接矩阵上标记,表示可以从 到达 ,然后从 开始宽搜,看能否到达 ,可以则输出 ,不能则输出 ,注意输出时不要漏了句号。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cfloat>
  7. #include <cstring>
  8. #include <string>
  9. #include <sstream>
  10. #include <vector>
  11. #include <list>
  12. #include <queue>
  13. #include <stack>
  14. #include <map>
  15. #include <set>
  16. #include <bitset>
  17. #include <algorithm>
  18. #include <ctime>
  19. #include <functional>
  20. #include <iomanip>
  21. using namespace std;
  22. #define LL long long
  23. const int maxn = 30;
  24. string str;
  25. queue<int> que;
  26. bool vis[maxn];
  27. bool G[maxn][maxn];
  28. bool bfs() {
  29. while(!que.empty()) {
  30. que.pop();
  31. }
  32. memset(vis, 0, sizeof(vis));
  33. vis['b' - 'a'] = true;
  34. que.push('b' - 'a');
  35. while(!que.empty()) {
  36. int tmp = que.front();
  37. que.pop();
  38. if(tmp == 'm' - 'a') {
  39. return true;
  40. }
  41. for(int i = 0; i < 26; ++i) {
  42. if(G[tmp][i] && !vis[i]) {
  43. vis[i] = true;
  44. que.push(i);
  45. }
  46. }
  47. }
  48. return false;
  49. }
  50. int main() {
  51. #ifdef LOCAL
  52. freopen("test.txt", "r", stdin);
  53. // freopen("out.txt", "w", stdout);
  54. #endif // LOCAL
  55. ios::sync_with_stdio(false);
  56. while(cin >> str) {
  57. if(str[0] == '0') {
  58. printf("No.\n");
  59. continue;
  60. }
  61. memset(G, 0, sizeof(G));
  62. G[str[0] - 'a'][str[str.length() - 1] - 'a'] = true;
  63. while(cin >> str, str[0] != '0') {
  64. G[str[0] - 'a'][str[str.length() - 1] - 'a'] = true;
  65. }
  66. if(bfs()) {
  67. printf("Yes.\n");
  68. } else {
  69. printf("No.\n");
  70. }
  71. }
  72. return 0;
  73. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注