首先要明确这样一个问题,只有边权为 1 时才能用BFS求最短路。
若边权不为1,移步图论算法解决。
基于之前所学的Flood Fill 算法,(12条消息) BFS 之Flood Fill 算法_Dream.Luffy的博客-CSDN博客
我们可以扩展出BFS对最短路径问题的求解。
由BFS Flood Fill算法的更新方式,我们可以得知 每一次更新到的点 的距离一定是最小的。
下面我们直接用几个例子来说明。
例一: 走迷宫
给定一个 n×m的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。
最初,有一个人位于左上角 (1,1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。
请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。
数据保证 (1,1) 处和 (n,m)处的数字为 0,且一定至少存在一条通路。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含 m 个整数(0 或 1),表示完整的二维数组迷宫。
输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数。
数据范围
1≤n,m≤100
输入样例:
- 5 5
- 0 1 0 0 0
- 0 1 0 1 0
- 0 0 0 0 0
- 0 1 1 1 0
- 0 0 0 1 0
输出样例:
8
算法分析:
对样例进行分析, 最短路径如下

由于每一条边的边权为1, 所以我们可以定义d数组来存储每个点到原点的距离,每次更新一个点时,距离+1 即可,
同时我们将这个数组初始化为-1, 用于标志该点是否已经被遍历过。
coding 一下:
- #include
- #include
- #include
- using namespace std;
- const int N = 110;
- typedef pair<int,int> PII;
- int n, m;
- int g[N][N];//存放地图
- int d[N][N];//存放每一个点到起点的距离
- PII q[N * N];//手写队列
- int bfs()
- {
- int hh = 0, tt = 0;
- q[0] = {0, 0};
- memset(d, - 1 ,sizeof d);//初始化距离为- 1;
- d[0][0] = 0;
- int dx[4] = {-1, 0, 1, 0}, dy[4] = {0,1, 0, -1};
-
- while(hh <= tt)//队列不为空时
- {
- PII t = q[hh ++];//取队头元素
-
- for(int i = 0;i < 4;i ++)//枚举4个方向
- {
- int x = t.first + dx[i];
- int y = t.second + dy[i];//在边界内且未被遍历过
- if( x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)
- {
- d[x][y] = d[t.first][t.second] + 1;
- q[ ++ tt ] = { x, y};//新坐标入队
-
- }
- }
- }
- return d[n - 1][m - 1];//输出右下角点距起点的距离
- }
那么我们进一步扩展,若题目要求我们输出最短路径呢?
我们看到这道题
迷宫问题2
给定一个 n×n 的二维数组,如下所示:
- int maze[5][5] = {
-
- 0, 1, 0, 0, 0,
-
- 0, 1, 0, 1, 0,
-
- 0, 0, 0, 0, 0,
-
- 0, 1, 1, 1, 0,
-
- 0, 0, 0, 1, 0,
-
- };
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。
数据保证至少存在一条从左上角走到右下角的路径。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含 n 个整数 0 或 1,表示迷宫。
输出格式
输出从左上角到右下角的最短路线,如果答案不唯一,输出任意一条路径均可。
按顺序,每行输出一个路径中经过的单元格的坐标,左上角坐标为 (0,0),右下角坐标为 (n−1,n−1)。
数据范围
0≤n≤1000
输入样例:
- 5
- 0 1 0 0 0
- 0 1 0 1 0
- 0 0 0 0 0
- 0 1 1 1 0
- 0 0 0 1 0
输出样例:
- 0 0
- 1 0
- 2 0
- 2 1
- 2 2
- 2 3
- 2 4
- 3 4
- 4 4
算法分析:
要打印路径,那么就需要存储路径,用数组存吗?可是我们并不知道最短路是哪一条。
所以这里,我们可以用一个数组pre 来存储 点{x,y} 是由哪个点扩展得到的, 也就是前驱节点。
例如 {1, 0} 是 由{0, 0} 扩展所得到的点, 所以pre[1][0] = {0, 0}

但是,这些点存在pre数组里,我们怎么将它打印成路径呢?
我们发现,由{0,0} 并不能得到{1, 0}以及后面的路径, 而{1, 0}却能得到{0,0},所以我们想到从后往前遍历, 存储每一个点,此时我们存储到的是倒序路径,所以最后再倒序枚举一遍即可
我们可以先将这些点依次遍历存在数组中,或栈中
方案一:存储在数组中
因为我们 是由后面的点存储前面的点坐标的 ,所以为了知道前面点的坐标,我们采取从后往前遍历。
每次将pre中的点加入vector中,最后再倒序枚举一遍即得到路径。
此时我们的遍历路径是绿色的这条线。(注意不要把pre的遍历路径和 迷宫路径混淆了, 迷宫路径是从0, 0 -> n - 1, n - 1) 我们这样遍历是为了求得路径方案。

- int a = n - 1, b = n - 1;
- vector
s; - while(true)
- {
- if(a == 0 && b == 0) break;
- s.push_back({a, b});
- PII t = pre[a][b];
- a = t.x, b = t.y;
- }
- s.push_back({0, 0}); // {0, 0}未被加入
-
- for(int i = s.size() - 1;i >= 0;i --)
- printf("%d %d\n",s[i].x, s[i].y);
因为{0,0}不具有前导点,所以{0,0}点在最后添加
方案二: 存储在栈中
思路与数组存储一致,只是换了个容器存储。
- stack
s; - int x = n - 1, y = n - 1;
- while(x || y){
- s.push({x, y});
- auto t = pre[x][y];
- x = t.x, y = t.y;
- }
- s.push({0, 0});
-
- //打印方案
- while(s.size()){
- PII t = s.top(); s.pop();
- cout << t.x << ' ' << t.y << endl;
- }
其实,还有种更为简便的办法,我们可以让出口作为入口, 入口作为出口
也就是从{n - 1, n - 1} 进入迷宫,从{0, 0}出迷宫
方案三:
所以可以得到以下图示:

我们可以发现,点{0,0}由pre[0][0] 可以得到点 {1,0} 点{n-2, n-1} 可以得到点{n-1, n-1},
前面的点可以更新出后面的点, 所以我们在打印最短路径时,只需正向枚举打印即可,而不需要开辟额外空间来存储路径。
所以核心就是: 逆序搜索,正序打印
代码如下:
-
- PII end = {0, 0};
-
- while (true)
- {
- printf("%d %d\n", end.x, end.y);
- if(end.x == n - 1 && end.y == n - 1) break; //走到终点时停止
- end = pre[end.x][end.y];
- }
coding一下:
- #include
- #include
- #include
-
- #define x first
- #define y second
-
- using namespace std;
-
- typedef pair<int, int> PII;
-
- const int N = 1010, M = N * N;
-
- int n;
- int g[N][N]; //记录地图
- PII q[M]; //队列
- PII pre[N][N]; //判断是否被遍历过, 同时存储上一个点的坐标
-
- void bfs(int sx , int sy)
- {
- int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
-
- int hh = 0, tt = 0;
- q[0] = {sx, sy};
-
- memset(pre, - 1, sizeof pre); //初始化为-1 用于表示是否被遍历过
- pre[sx][sy] = {0, 0}; //初始点可以随意设置,但不能设置为 -1
- while(hh <= tt)
- {
- PII t = q[hh ++];
-
- for(int i = 0;i < 4;i ++)
- {
- int a = t.x + dx[i], b = t.y + dy[i];
- if(a < 0 || a >= n || b < 0 || b >= n) continue;
- if(g[a][b]) continue; //如果是墙,就不能走过去
- if(pre[a][b].x != -1) continue; //若该点未被遍历过
-
- q[ ++ tt] = {a, b};
- pre[a][b] = t;
- }
- }
- }
- int main()
- {
- scanf("%d", &n);
-
- for(int i = 0;i < n;i ++)
- for(int j = 0;j < n;j ++)
- scanf("%d", &g[i][j]);
-
- bfs(n - 1, n - 1); // 从终点往起点遍历
-
- PII end = {0, 0};
-
- while (true)
- {
- printf("%d %d\n", end.x, end.y);
- if(end.x == n - 1 && end.y == n - 1) break; //走到终点时停止
- end = pre[end.x][end.y];
- }
-
- return 0;
- }
方案一和二的代码如下:
- #include
- #include
- #include
- #include
- #define x first
- #define y second
- using namespace std;
- typedef pair<int,int> PII;
- const int N = 1010;
-
- int n;
- PII q[N * N], pre[N][N];
- int g[N][N];
-
- void bfs(int sx,int sy)
- {
- int hh = 0, tt = 0;
- int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
-
- q[0] = {sx,sy};
-
- memset(pre, -1, sizeof pre);
- pre[sx][sy] = {0, 0}; //初始点可以随意设置,但不能设置为 -1
- while(hh <= tt)
- {
- PII t = q[hh ++];
- for(int i = 0;i < 4;i ++)
- {
- int a = t.x + dx[i], b = t.y + dy[i];
- if(a < 0 || a >= n || b < 0 || b >= n) continue;
- if(pre[a][b].x != -1) continue;
- if(g[a][b]) continue;
- q[ ++ tt] = {a, b};
- pre[a][b] = {t.x,t.y};
- }
- }
- }
- int main()
- {
- cin >> n;
-
- for(int i = 0;i < n;i ++)
- for(int j = 0;j < n;j ++)
- scanf("%d",&g[i][j]);
-
- bfs(0, 0);
-
- //方案一
- int a = n - 1, b = n - 1;
- vector
s; - while(true)
- {
- if(a == 0 && b == 0) break;
- s.push_back({a, b});
- PII t = pre[a][b];
- a = t.x, b = t.y;
- }
- s.push_back({0, 0}); // {0, 0}未被加入
-
- for(int i = s.size() - 1;i >= 0;i --)
- printf("%d %d\n",s[i].x, s[i].y);
-
- //方案二
- // stack
s; - // int x = n - 1, y = n - 1;
- // while(x || y){
- // s.push({x, y});
- // PII t = pre[x][y];
- // x = t.x, y = t.y;
- // }
- // s.push({0, 0});
-
- // //打印方案
- // while(s.size()){
- // PII t = s.top(); s.pop();
- // cout << t.x << ' ' << t.y << endl;
- // }
- return 0;
- }
希望本文对你有帮助
该系列会持续更新, 我是Luffy,期待与你再次相遇
