C++知识点总结(41):广度优先搜索
200*注:如果还没有学过或者的同学请仔细看完我的总结哦~
广度优先搜索
*注:如果还没有学过 结构体 或者 栈/队列/优先队列 的同学请仔细看完我的总结哦~
一、广度优先搜索
1. 意义
广度优先搜索(Breadth-first search,简称BFS),别名宽度优先搜索,是一种搜索算法。但是相较于深度优先搜索(Depth-first search,简称DFS),对于某些题目,时间复杂度更低。举个例子,对于走迷宫哪条路径最短,BFS 找来很多人一起走,只要出现有人走到终点的情况,那么这条路径必定是最短的,而 DFS 不能。
2. 分析
假如有这样一个迷宫:
1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|
1 | * | ||||
2 | * | * | |||
3 | |||||
4 | * | * | * | ||
5 | * |
步数 | 位置 | 存活 |
---|---|---|
0 | (1,1) | 1 |
1 | (2,1) | 1 |
2 | (3,1) | 2 |
3 | (4,1)/(3,2) | 1 / 1 |
4 | (3,3)/(5,1) | 2 / 1 |
5 | (2,3)(3,4)/(5,2) | … |
… | … | … |
3. 基本程序
首先我们要理解为什么要用队列,这是因为在每次人没有存活的时候就要删除一开始的元素,那么用队列会更方便。
结构体队列的定义方法:
#include <queue>
struct Node
{
int x;
bool flag;
char c;
}; // 记得写分号
queue <Node> q;
q.push({1, 0, 'c'}); // 入队
auto [a, b, c] = q.front(); // 获取队首元素
cout << a << " " << b << " " << c; // 输出: 1 0 'c'
伪代码如下:
void bfs()
{
vis[1][1] = 1; // 起点标记
q.push({1, 1, 0}); // 起点入队
while (!q.empty) // 非空时
{
// 获取队首元素并出队
// 判断是否为终点
// 是:结束
// 遍历四个方向
// 获得新的x,y坐标,存为tx,ty
// 判断能否走(边界,障碍,访问)
// 能走:tx,ty,step+1入队
}
}
二、例题
1. 最短路径模板
#include <iostream>
#include <queue>
using namespace std;
struct Node
{
int x, y; // 当前坐标
int step; // 当前步数
};
int n;
int a[105][105];
int sx, sy, ex, ey;
queue <Node> q; // 结构体队列
bool vis[105][105];
int dx[5] = {-1, 0, 0, 1};
int dy[5] = {0, -1, 1, 0};
void bfs()
{
vis[sx][sy] = 1; // 起点标记
q.push({sx, sy, 0}); // 起点入队
while (!q.empty()) // 非空时
{
// 获取队首元素并出队
auto [fx, fy, fstep] = q.front();
q.pop();
// 判断是否为终点
if (fx==ex && fy==ey)
{
cout << fstep;
return;
}
// 遍历四个方向
for (int i = 0; i <= 3; i++)
{
int tx = fx+dx[i];
int ty = fy+dy[i];
if (a[tx][ty]==0 && tx>=1 && tx<=n && ty>=1 && ty<=n && vis[tx][ty]==0)
{
vis[tx][ty] = 1;
q.push({tx, ty, fstep+1});
}
}
}
}
int main()
{
// 输入
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> a[i][j];
cin >> sx >> sy >> ex >> ey;
// bfs
bfs();
return 0;
}
2. 跳跃的马
2.1 审题
根据国际象棋规则,马只能走日字格。现在给定你一个
n
×
m
n\times m
n×m 的矩阵,已知小马在矩阵的 (x, y)
,请你给出一个步数矩阵,计算每个格子到达的步数(不能到达就填充 -1
)。文件:horse.cpp
。要求行末尾没有空格。
输入:
3 3 1 1
输出:
0 3 2
3 -1 1
2 1 4
2.2 思路
明确日子格的偏移量!
x x x 偏移量 | y y y 偏移量 |
---|---|
-2 | -1 |
-1 | -2 |
1 | -2 |
2 | -1 |
-2 | 1 |
-1 | 2 |
1 | 2 |
2 | 1 |
2.3 参考答案
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
struct Node
{
int x, y;
int step;
};
int n, m;
int x, y;
int a[105][105];
int dx[10] = {-2, -1, 1, 2, -2, -1, 1, 2};
int dy[10] = {-1, -2, -2, -1, 1, 2, 2, 1};
queue <Node> q;
void bfs()
{
a[x][y] = 0; // 起点标记
q.push({x, y, 0}); // 起点入队
while (!q.empty()) // 非空时
{
// 获取队首元素并出队
auto [fx, fy, fstep] = q.front();
q.pop();
// 遍历八个方向
for (int i = 0; i <= 7; i++)
{
int tx = fx+dx[i];
int ty = fy+dy[i];
if (tx>=1 && tx<=n && ty>=1 && ty<=m && a[tx][ty]==-1)
{
a[tx][ty] = fstep+1;
q.push({tx, ty, fstep+1});
}
}
}
}
int main()
{
freopen("horse.in", "r", stdin);
freopen("horse.out", "w", stdout);
// 输入
cin >> n >> m >> x >> y;
memset(a, -1, sizeof(a));
// bfs
bfs();
// 输出
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cout << a[i][j] << " \n"[j==m];
}
}
fclose(stdin);
fclose(stdout);
return 0;
}
3. 今天就上 200 200 200 层
3.1 审题
题目描述
《今天就上 200 200 200 层》是一款考验玩家策略的益智类游戏,游戏中需要操控 yls 上到摩天大楼的第 200 200 200 层,摩天大楼的每一层都有一个光圈,光圈上会显示一个数字 k i k_i ki,光圈有 2 2 2 个箭头,一个向上,一个向下,上下的层数等于光圈上的数字,但注意,并不是所有的光圈都是有效的,例如 3 3 3 3 3 3 1 1 1 2 2 2 5 5 5 代表 k i k_i ki( k 1 = 3 k_1 = 3 k1=3, k 2 = 3 k_2 = 3 k2=3…),如果 yls 当前在 1 1 1 层,选择"上"的箭头可以到达 4 4 4 层,选择"下"的箭头是不起作用的,因为没有 − 2 -2 −2 层。
假设 yls 在 x x x 层,想要到 y y y 层,至少需要选择几次光圈?
输入描述
输入共 2 2 2 行。
第一行为三个用空格隔开的正整数,表示 n n n(表示摩天大楼的层数) , x , y ,x,y ,x,y( 1 ≤ n ≤ 200 1 \le n \le 200 1≤n≤200, 1 ≤ x , y ≤ n 1 \le x, y \le n 1≤x,y≤n)。
第二行为 n n n 个用空格隔开的非负整数,表示 k i k_i ki( 1 ≤ k i ≤ n 1 \le k_i \le n 1≤ki≤n)。
输出描述
一行,即最少按键次数,若无法到达,则输出 − 1 -1 −1。
样例1
输入
5 1 5 3 3 1 2 5
输出
3
3.2 参考答案
#include <iostream>
#include <queue>
using namespace std;
struct Node
{
int f;
int step;
};
int n;
int s, e;
int k[205];
bool vis[205];
queue <Node> q;
void bfs()
{
vis[s] = 1; // 起点标记
q.push({s, 0}); // 起点入队
while (!q.empty())
{
// 获取队首元素并出队
auto [ff, fstep] = q.front();
q.pop();
// 判断是否为终点
if (ff == e)
{
cout << fstep;
return;
}
// 往上走
int up = ff+k[ff];
if (up<=n && vis[up]==0)
{
vis[up] = 1;
q.push({up, fstep+1});
}
// 往下走
int down = ff-k[ff];
if (down>=1 && vis[down]==0)
{
vis[down] = 1;
q.push({down, fstep+1});
}
}
cout << -1;
return;
}
int main()
{
// 输入
cin >> n >> s >> e;
for (int i = 1; i <= n; i++)
cin >> k[i];
// bfs
bfs();
return 0;
}
三、DFS VS. BFS
BFS 适用于求源点和目标结点距离近的情况,例如求最少步数;
DFS 适用于求解一个任意符合方案中的一种或者遍历所有情况,例如能否到达、求所有路径数、全排列。
对于不同的题目,二者的优势也各不相同,在今天的三道例题中都推荐用时间复杂度更低的 BFS。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)