广度优先搜索(模板使用)
模板出处
-
关于模板出处,来自这里
-
本文仅通过例题对模板的使用进行说明。
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn=100;
bool vst[maxn][maxn]; // 访问标记
int dir[4][2]={0,1,0,-1,1,0,-1,0}; // 方向向量
struct State{ // BFS 队列中的状态数据结构
int x,y; // 坐标位置
int Step_Counter; // 搜索步数统计器
};
State a[maxn];
bool CheckState(State s){ // 约束条件检验
if(!vst[s.x][s.y] && ...) // 满足条件
return 1;
else // 约束条件冲突
return 0;
}
void bfs(State st){
queue <State> q; // BFS 队列
State now,next; // 定义2 个状态,当前和下一个
st.Step_Counter=0; // 计数器清零
q.push(st); // 入队
vst[st.x][st.y]=1; // 访问标记
while(!q.empty()){
now=q.front(); // 取队首元素进行扩展
if(now==G){ //出现目标态,此时为Step_Counter 的最小值,可以退出即可
...... // 做相关处理
return;
}
for(int i=0;i<4;i++){ // 如果状态满足约束条件则入队
next.x=now.x+dir[i][0]; // 按照规则生成下一个状态
next.y=now.y+dir[i][1];
next.Step_Counter=now.Step_Counter+1; // 计数器加1
if(CheckState(next)){
q.push(next);
vst[next.x][next.y]=1; //访问标记
}
}
q.pop(); // 队首元素出队
}
return;
}
int main()
{
......
return 0;
}
例题1:油田
题目说明:
某石油勘探公司正在按疾患勘探地下油田资源,在一片长方形地域中工作。他们首先将该地域划分为许多小正方形区域,然后使用勘探设备分别探测在每一小正方形区域内是否有油。含有油的区域被称为油田。如果两个油田相邻(在水平、垂直或对角线相邻),则它们是相同油藏的一部分。油藏可能非常大并可能包含许多油田(油田的个数不超过100)。你的工作是确定在这个长方形地域中包含多少不同的油藏。
输入:输入文件包含一个或多个长方形地域。每个地域的第1行都有两个正整数 m m m和 n ( 1 ≤ m , n ≤ 100 ) n(1 \leq m,n \leq 100) n(1≤m,n≤100),表示地域的行数和列数。如果 m = 0 m = 0 m=0,则表示输入结束;否则此后有 m m m行,每行都有 n n n个字符。每个字符都对应一个正方形区域,字符*表示没有油,字符@表示有油。
输出:对于每个长方形地域,都单行输出油藏的个数。
算法设计
-
使用广度优先搜索模板进行求解
-
约束条件为坐标出界限制,对应点是否为油田,标记数组。共3个
#include <bits/stdc++.h>
using namespace std;
const int maxn = 10;
int m,n; //行数,列数
int ans = 0; //油田数量
bool vst[maxn][maxn]; // 访问标记
char str[maxn][maxn]; //油田矩阵
int dir[8][2]={-1,-1,-1,0,-1,1,
0,-1,0,1,1,-1,
1,0,1,1}; // 方向向量
struct state{ // BFS 队列中的状态数据结构
int x,y; // 坐标位置
int step; // 搜索步数统计器
};
bool check(state s); //约束条件检验
void bfs(int i,int j); //广度优先搜索
int main(){
memset(vst,false,sizeof(vst)); //标记数组初始化
cin >> m >> n;
for (int i = 0; i < m; ++i) { //存储油田
for (int j = 0; j < n; ++j) {
cin >> str[i][j];
}
}
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if(str[i][j] == '@' && !vst[i][j]){
bfs(i,j);
ans++;
}
}
}
cout << ans << endl;
return 0;
}
bool check(state s){ // 约束条件检验
if(s.x >= 0 && s.x < m && s.y >= 0 && s.y < n && str[s.x][s.y] == '@' && !vst[s.x][s.y]){
return true;
}
return false;
}
void bfs(int i,int j){
queue<state> q; //BFS队列
state now,next; //定义2个状态,当前和下一个
next.x = i; //将油田入队
next.y = j;
next.step = 0;
q.push(next);
vst[next.x][next.y] = true;
while(!q.empty()){
now = q.front(); //取队首元素扩展
for (int k = 0; k < 8; ++k) { //8个方向扩展
next.x = now.x+dir[k][0]; //按照规则生成下一个状态
next.y = now.y+dir[k][1];
next.step = now.step+1;
if(check(next)){ //判断是否满足约束条件
q.push(next);
vst[next.x][next.y] = true; //标记访问
}
}
q.pop(); //队首元素出队
}
}
输入:
5 5
****@
*@@*@
*@**@
@@@*@
@@**@
输出:
2
例题2:抓住那头牛
题目描述
约翰希望立即抓住逃亡的牛。当前约翰在节点 N N N,牛在节点 K ( 0 ≤ N , K ≤ 100000 ) K(0 \leq N,K \leq 100000) K(0≤N,K≤100000)时,他们在同一条线上。约翰有两种交通方式:步行和乘车。如果牛不知道有人在追赶自己,原地不动,那么约翰需要多长时间才能抓住牛?
- 步行:约翰可以在一分钟内从任意节点 X X X移动到节点 X − 1 X-1 X−1或 X + 1 X+1 X+1。
- 乘车:约翰可以在一分钟内从任意节点X移动到节点 2 × X 2 \times X 2×X。
输入:两个整数 N N N和 K K K。
输出:单行输出约翰抓住牛所需的最短时间(以分钟为单位)。
算法设计
-
此题是在一维空间下的,与二维空间略有不同。
-
移动方式有改变,这一点在更新状态方面可以看出。
-
约束条件为,一维坐标大于0且小于50(这里的50主要是为了防止无意义的搜索,因为一旦超过50,即使往回走也一定不是最优解,所以要根据牛所处的位置而定),标记数组,共3个
#include <bits/stdc++.h>
using namespace std;
const int maxn = 20;
int n,k;//约翰的距离、牛的距离
bool vst[maxn]; // 访问标记
struct state{ // BFS 队列中的状态数据结构
int x; // 坐标位置
int time; // 搜索用时间
};
bool check(state s); //约束条件函数
void bfs(int temp); //广度优先搜索
int main(){
cin >> n >> k;
memset(vst,false,sizeof(vst)); //标记数组初始化
bfs(n); //起点为5,从5开始进行广度优先搜索
}
bool check(state s){ // 约束条件检验
if(s.x >= 0 && s.x <= 50 && !vst[s.x]){
return true;
}
return false;
}
void bfs(int temp){
queue<state> q;
state now,next; // 定义2 个状态,当前和下一个
next.x = temp;
next.time = 0;
q.push(next);
vst[temp] = true; //标记访问
while(!q.empty()){
now = q.front();
if(now.x == k){ //出现目标态,输出时间,退出即可
cout << now.time << endl;
return;
}
next.x = now.x+1; //按照规则生成下一个状态
if(check(next)){
next.time = now.time + 1; //更新下一个状态的时间
vst[next.x] = true; //访问标记
q.push(next); //入队
}
next.x = now.x-1; //按照规则生成下一个状态
if(check(next)){
next.time = now.time + 1; //更新下一个状态的时间
vst[next.x] = true; //访问标记
q.push(next); //入队
}
next.x = now.x*2; //按照规则生成下一个状态
if(check(next)){
next.time = now.time + 1; //更新下一个状态的时间
vst[next.x] = true; //访问标记
q.push(next); //入队
}
q.pop(); //出队
}
}
输入:
5 17
输出:
4
试题C:扩散
问题描述
小蓝在一张无限大的特殊画布上作画。
这张画布可以看成一个方格图,每个格子可以用一个二维的整数坐标表示。
小蓝在画布上首先点了一下几个点(0,0)、(2020,11)、(11,14)、(2000,2000)。只有这几个格子上有黑色,其他位置都是白色的。
每过一分钟,黑色就会扩散一点。具体的,如果一个格子里面全是黑色,它就会扩散到上、下、左、右四个相邻的格子中,使得这四个格子也变成黑色(如果原来就是黑色,则还是黑色)。
请问,经过2020分钟后,画布上有多少个格子是黑色的。
算法设计
- 暴力求解很容易爆内存,所以选用BFS(广度优先搜索)算法是一种方法。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 10000;
int ans = 4; //已经存在4个黑点
bool vst[maxn][maxn]; //访问标记
int dir[4][2] = {0,1,0,-1,1,0,-1,0}; //方向向量
struct State{
int x,y; //坐标位置
int step; //搜索步数统计器
};
bool check(State s); //约束条件检验函数
void bfs(); //广度优先搜索
int main(){
memset(vst,false,sizeof(vst)); //标记数组初始化
bfs();
cout << ans << endl;
return 0;
}
bool check(State s){
if(!vst[s.x][s.y] && s.step<=2020){ //坐标未被访问,且步数(即时间)小于等于2020
return true;
}
return false;
}
void bfs(){
queue<State> q; //BFS队列
State now,next; //当前状态,下一个状态
vst[3000][3000]=vst[5020][3011]=vst[3011][3014]=vst[5000][5000]=true;
next.x=3000,next.y=3000,next.step=0;
q.push(next);
next.x=5020,next.y=3011,next.step=0;
q.push(next);
next.x=3011,next.y=3014,next.step=0;
q.push(next);
next.x=5000,next.y=5000,next.step=0;
q.push(next);
while(!q.empty()){
now = q.front();
for (int i = 0; i < 4; ++i) {
next.x = now.x+dir[i][0];
next.y = now.y+dir[i][1];
next.step = now.step+1;
if(check(next)){
q.push(next);
vst[next.x][next.y] = true; //标记访问
ans++;
}
}
q.pop(); //出队
}
}
输出:
20312088