提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
BFS
前言
区分依据 | BFS(广度优先搜索) | DFS(深度优先搜索) |
---|---|---|
实现方式 | 队列实现 | 递归实现 |
复杂度 | 空间复杂度大 | 时间复杂度大 |
唯美表达 | 一石惊起千层浪 | 不撞南墙不回头 |
应用场景 | 求最短路径 | 暂时不知道空着嘿嘿 |
一、简单问题试水
1.离开中山路
题目背景
《爱与愁的故事第三弹·shopping》最终章。
题目描述
爱与愁大神买完东西后,打算坐车离开中山路。现在爱与愁大神在 x 1 , y 1 x_1,y_1 x1,y1 处,车站在 x 2 , y 2 x_2,y_2 x2,y2 处。现在给出一个 n × n ( n ≤ 1000 ) n \times n(n \le 1000) n×n(n≤1000) 的地图, 0 0 0 表示马路, 1 1 1 表示店铺(不能从店铺穿过),爱与愁大神只能垂直或水平着在马路上行进。爱与愁大神为了节省时间,他要求最短到达目的地距离(每两个相邻坐标间距离为 1 1 1)。你能帮他解决吗?
输入格式
第 1 1 1 行包含一个数 n n n。
第 2 2 2 行到第 n + 1 n+1 n+1 行:整个地图描述( 0 0 0 表示马路, 1 1 1 表示店铺,注意两个数之间没有空格)。
第 n + 2 n+2 n+2 行:四个数 x 1 , y 1 , x 2 , y 2 x_1,y_1,x_2,y_2 x1,y1,x2,y2。
输出格式
只有 1 1 1 行,即最短到达目的地距离。
样例 #1
样例输入 #1
3
001
101
100
1 1 3 3
样例输出 #1
4
提示
对于 20 % 20\% 20% 数据,满足 1 ≤ n ≤ 100 1\leq n \le 100 1≤n≤100。
对于 100 % 100\% 100% 数据,满足 1 ≤ n ≤ 1000 1\leq n \le 1000 1≤n≤1000。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int, int> PII;//存坐标
const int N = 1100;
int n,x,y,x2,y2;
PII start = { x,y };
int g[N][N];
int dist[N][N]; //距离
int dx[] = { -1,0,1,0 };
int dy[] = { 0,1,0,-1 };
queue<PII> q;//队列
int bfs(int x1, int y1) {
memset(dist, -1, sizeof dist);
q.push({ x1,y1 });
dist[x1][y1] = 0;
while (q.size()) { //队列不空,继续循环;
auto t = q.front();//取出队头
q.pop();//弹出
for (int i = 0; i < 4; i++) {
int a = t.first + dx[i];
int b = t.second + dy[i];
if (a<1 || a>n || b<1 || b>n)continue;
if (g[a][b] != 0)continue;
if (dist[a][b] > 0)continue;
q.push({ a,b });
dist[a][b] = dist[t.first][t.second]+1;
}
}
return dist[x2][y2];
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%1d",&g[i][j]);//恶心
}
}
cin >> x >> y >> x2 >> y2;
int res = bfs(x, y);
printf("%d", res);
return 0;
}
2.洛谷-P1443-马的遍历
解法与上题类似
3.洛谷-P1332-血色先锋队
多个感染源,解法也类似,只是在开始时把多个感染源一起入队。
4.填涂颜色
题目描述
由数字 0 0 0 组成的方阵中,有一任意形状的由数字 1 1 1 构成的闭合圈。现要求把闭合圈内的所有空间都填写成 2 2 2。例如: 6 × 6 6\times 6 6×6 的方阵( n = 6 n=6 n=6),涂色前和涂色后的方阵如下:
如果从某个 0 0 0 出发,只向上下左右 4 4 4 个方向移动且仅经过其他 0 0 0 的情况下,无法到达方阵的边界,就认为这个 0 0 0 在闭合圈内。闭合圈不一定是环形的,可以是任意形状,但保证闭合圈内的 0 0 0 是连通的(两两之间可以相互到达)。
0 0 0 0 0 0
0 0 0 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 1 0 1
1 1 1 1 1 1
0 0 0 0 0 0
0 0 0 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 1 2 1
1 1 1 1 1 1
输入格式
每组测试数据第一行一个整数 n ( 1 ≤ n ≤ 30 ) n(1 \le n \le 30) n(1≤n≤30)。
接下来 n n n 行,由 0 0 0 和 1 1 1 组成的 n × n n \times n n×n 的方阵。
方阵内只有一个闭合圈,圈内至少有一个 0 0 0。
输出格式
已经填好数字 2 2 2 的完整方阵。
样例 #1
样例输入 #1
6
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1
样例输出 #1
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1
提示
对于
100
%
100\%
100% 的数据,
1
≤
n
≤
30
1 \le n \le 30
1≤n≤30。
tip:两种解法 1:找圈内联通块 (my answer) 2:找圈外联通块,把范围扩大一圈,那么外围就全部联通成一块了
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int, int> PII;//存坐标
const int N = 40;
int g[N][N];
bool st[N][N];
int dist[N][N];
int n;
int flag = 0;
queue<PII> q;
int dx[] = { 0,1,0,-1 };
int dy[] = { 1,0,-1,0 };
void bfs(int x, int y) {
if (x == 1 || y == 1 || x == n || y == n)return;
memset(st, -1, sizeof st);
memset(dist, -1, sizeof dist);
st[x][y] = 0;
dist[x][y] = 0;
q.push({ x,y });
while (q.size()) {
auto t = q.front();
q.pop();
for (int i = 0; i < 4; i++) {
int a = t.first + dx[i], b = t.second + dy[i];
if (g[a][b] == 1) continue;
if (dist[a][b] >= 0)continue;
if (a == 1 || b == 1 || a == n || b == n) {
while (q.size()) {
q.pop();
}
return;
}
dist[a][b] = 1;
st[a][b] = 0;
q.push({ a,b });
}
}
flag = 1;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%d", &g[i][j]);
}
}
for (int i = 2; i <= n; i++) {
for (int j = 2; j <= n; j++) {
if (flag == 1) {
break;
}
if (g[i][j] == 0) {
bfs(i, j);
}
}
if (flag == 1)break;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (st[i][j] == 0) {
printf("2 ");
}
else {
printf("%d ", g[i][j]);
}
}
printf("\n");
}
return 0;
}
二、难度增加
1.汽车拉力比赛
题目描述
博艾市将要举行一场汽车拉力比赛。
赛场凹凸不平,所以被描述为 N ∗ M N*M N∗M 的网格来表示海拔高度 ( 1 ≤ M , N ≤ 500 ) (1 \leq M,N \leq 500) (1≤M,N≤500),每个单元格的海拔范围在 0 0 0 到 1 0 9 10^9 109 之间。
其中一些单元格被定义为路标。组织者希望给整个路线指定一个难度系数 D D D,这样参赛选手从任一路标到达别的路标所经过的路径上相邻单元格的海拔高度差不会大于 D D D 。也就是说这个难度系数 D D D 指的是保证所有路标相互可达的最小值。任一单元格和其东西南北四个方向上的单元格都是相邻的。
输入格式
第 1 1 1 行两个整数 M M M 和 N N N。第 2 2 2 行到第 M + 1 M+1 M+1 行,每行 N N N 个整数描述海拔高度。第 2 + M 2+M 2+M 行到第 1 + 2 M 1+2M 1+2M
行,每行 N N N 个整数,每个数非 0 0 0 即 1 1 1, 1 1 1 表示该单元格是一个路标。
输出格式
一个整数,即赛道的难度系数 D D D。
样例 #1
样例输入 #1
3 5
20 21 18 99 5
19 22 20 16 26
18 17 40 60 80
1 0 0 0 1
0 0 0 0 0
0 0 0 0 1
样例输出 #1
21
tips:二分加bfs 笔者一开始的想法是求每条路径的D,然后综合求最后的D,显然很难实现。
#include<bits/stdc++.h>
using namespace std;
const int N = 502;
typedef pair<int, int> PII;
int m, n, x,y ;
int high[N][N];//记录海拔
bool sign[N][N];//记录路标
int flag_sum = 0;//路标数量
int dx[] = { 0,-1,0,1 };
int dy[] = { 1,0,-1,0 };
bool st[N][N];//记录是否走过
queue<PII> q;
bool bfs(int d) {
int count = 0;
memset(st, 0, sizeof st);
q.push({ x,y });
while (q.size()) {
auto t = q.front();
q.pop();
for (int i = 0; i < 4; i++) {
int a = t.first + dx[i], b = t.second + dy[i];
if (a<0 || a>=m || b<0 || b>=n)continue;
if (st[a][b])continue;
if (d < abs(high[a][b] - high[t.first][t.second]))continue;
q.push({ a,b });
st[a][b] = 1;
if (sign[a][b]) {
count++;
}
}
if (count == flag_sum) {
while (q.size()) {
q.pop();
}
return true;
}
}
return false;
}
int main() {
scanf("%d %d", &m, &n);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
scanf("%d", &high[i][j]);
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
scanf("%d", &sign[i][j]);
if (sign[i][j]) {
flag_sum++;
x= i;
y = j;
}
}
}
//二分查找
int l = -1, r = 1e9 + 10;
while (l + 1 < r) {
int mid = (l + r) / 2;
if (bfs(mid)) {
r = mid;
}
else {
l = mid;
}
}
printf("%d\n", r);
return 0;
}
2.洛谷-4554-小明的游戏
tips:双端队列deque
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N = 502;
char g[N][N];
int n, m,x1,y1,x2,y2;
typedef pair<int, int> PII;
deque<PII>q;
int dist[N][N];
int dx[] = { 0,1,0,-1 };
int dy[] = { 1,0,-1,0 };
int bfs(int x, int y) {
q.clear();//清空队列
memset(dist, -1, sizeof dist);
q.push_back({ x,y });
dist[x][y] = 0;
while (q.size()) {
auto t = q.front();
q.pop_front();
char ch = g[t.first][t.second];
for (int i = 0; i < 4; i++) {
int a = t.first + dx[i], b = t.second + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m)continue;
if (dist[a][b]>=0)continue;
if (g[a][b] == ch) {
dist[a][b] = dist[t.first][t.second];
q.push_front({ a,b });
}
else {
dist[a][b] = dist[t.first][t.second]+1;
q.push_back({ a,b });
}
if (a == x2 && b == y2)return dist[a][b];
}
}
return -1;
}
int main() {
while (cin >> n >> m, n || m) {
for (int i = 0; i < n; i++) {
scanf("%s", g[i]);
}
cin >> x1 >> y1 >> x2 >> y2;
int res = bfs(x1, y1);
cout << res << endl;
}
return 0;
}
3.八数码难题
题目描述
在 3 × 3 3\times 3 3×3 的棋盘上,摆有八个棋子,每个棋子上标有 1 1 1 至 8 8 8 的某一数字。棋盘中留有一个空格,空格用 0 0 0 来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为 123804765 123804765 123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
输入格式
输入初始状态,一行九个数字,空格用 0 0 0 表示。
输出格式
只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数。保证测试数据中无特殊无法到达目标状态数据。
样例 #1
样例输入 #1
283104765
样例输出 #1
4
提示
样例解释
图中标有 0 0 0 的是空格。绿色格子是空格所在位置,橙色格子是下一步可以移动到空格的位置。如图所示,用四步可以达到目标状态。
并且可以证明,不存在更优的策略。
tips:1.二维数组用一维数组表示 2.用哈希表存储每个字符串状态对应的步数
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<unordered_map>
using namespace std;
string ans = "123804765";
unordered_map<string, int>dist;
queue<string>q;
int dx[] = { 0,1,0,-1 };
int dy[] = { 1,0,-1,0 };
int bfs(string start) {
q.push(start);
dist[start] = 0;
while (q.size()) {
auto t = q.front();
if (t == ans) {
return dist[t];
}
q.pop();
int distance = dist[t];
int a = t.find('0');
int x1 = a / 3, y1 = a % 3;//一维数组转化为二维矩阵
for (int i = 0; i < 4; i++) {
int x2 = x1 + dx[i], y2 = y1 + dy[i];
if (x2 < 0 || x2 >= 3 || y2 < 0 || y2 >= 3)continue;
int tmp = x2 * 3 + y2;
swap(t[a], t[tmp]);
if (!dist.count(t)) {
dist[t] = distance + 1;
q.push(t);
}
swap(t[a], t[tmp]);
}
}
return -1;
}
int main() {
string start;
cin >> start;
int res = bfs(start);
cout << res <<endl;
}
三.双向bfs!!!
适用情况:起点与终点状态已知
优势:时间更少,空间更少
实现方式:与普通bfs相比,双向bfs维护的是两个队列,然后轮流拓展两个队列。同时用数组或哈希表记录当前的搜索情况,给从两个方向拓展的节点以不同的标记。当某点被两种标记同时标记时,搜索结束。
1.离开中山路优化(1746)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<unordered_map>
using namespace std;
typedef pair<int, int>PII;
const int N = 1002;
int n;
int x3, y3, x2, y2;
char g[N][N];
PII q[N * N];
int dist[N][N];
int vis[N][N];//标记
int dx[] = { 0,1,0,-1 };
int dy[] = { 1,0,-1,0 };
int bfs() {
memset(dist, -1, sizeof dist);
memset(vis, -1, sizeof vis);
q[0] = { x3,y3 },q[1] = { x2,y2 };
dist[x3][y3] = 0,dist[x2][y2] = 0;
vis[x3][y3] = 1,vis[x2][y2] = 2;
int hh = 0, tt = 2;
while (hh < tt) {
auto t = q[hh++];
for (int i = 0; i < 4; i++) {
int a = t.first + dx[i], b = t.second + dy[i];
if (a < 1 || a > n || b < 1 || b > n)continue;
if (g[a][b] == '1')continue;
if (vis[a][b] + vis[t.first][t.second] == 3)return dist[a][b] + dist[t.first][t.second] + 1;
if (dist[a][b] >= 0)continue;
if(vis[a][b]==-1)vis[a][b] = vis[t.first][t.second];
dist[a][b] = dist[t.first][t.second] + 1;
q[tt++] = { a,b };
}
}
return -1;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%s", g[i]+1);
}
cin >> x3 >> y3 >> x2 >> y2;
printf("%d", bfs());
return 0;
}
2.八数码难题优化
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<unordered_map>
using namespace std;
string ans = "123804765";
string start;
unordered_map<string, int>dist;
unordered_map<string, int>vis;
queue<string>q;
int dx[] = { 0,1,0,-1 };
int dy[] = { 1,0,-1,0 };
int bfs() {
if (start == ans)return 0;
q.push(start);
q.push(ans);
dist[start] = 0,dist[ans] = 0;
vis[start] = 1, vis[ans] = 2;
while (q.size()) {
auto t = q.front();
q.pop();
int distance = dist[t];
int a = t.find('0');
int x1 = a / 3, y1 = a % 3;//一维数组转化为二维矩阵
for (int i = 0; i < 4; i++) {
int x2 = x1 + dx[i], y2 = y1 + dy[i];
if (x2 < 0 || x2 >= 3 || y2 < 0 || y2 >= 3)continue;
int tmp = x2 * 3 + y2;
string t1 = t;
swap(t[a], t[tmp]);
if (vis[t1] + vis[t] == 3)return dist[t1] + dist[t] + 1;
if (!dist.count(t)) {
dist[t] = distance + 1;
vis[t] = vis[t1];
q.push(t);
}
swap(t[a], t[tmp]);
}
}
return -1;
}
int main() {
cin >> start;
int res = bfs();
cout << res << endl;
}
总结
道阻且长,我们一起加油
下一站——DP!!!