广度优先搜索复习笔记
前言
BFS 全称是 \(Breadth First Search\),中文名是宽度优先搜索,也叫广度优先搜索。
是图上最基础、最重要的搜索算法之一。
所谓宽度优先。就是每次都尝试访问同一层的节点。 如果同一层都访问完了,再访问下一层。
这样做的结果是,BFS 算法找到的路径是从起点开始的 最短 合法路径。换言之,这条路所包含的边数最小。
在 BFS 结束时,每个节点都是通过从起点到该点的最短路径访问的。
算法过程可以看做是图上火苗传播的过程:最开始只有起点着火了,在每一时刻,有火的节点都向它相邻的所有节点传播火苗。
当然也可以应用于其他的一些结构中进行搜索
框架
void BFS()
{
memset(vis,0,sizeof(vis));
queue<数据类型> q;
q.push(初始值);
vis[初始值]=1;
while(!q.empty())
{
int k=q.front();
q.pop();
//....操作,处理
if(符合条件)
{
q.push(当前节点);
vis[当前节点]=1;
}
}
}
例题讲解
P2730 [USACO3.2]魔板 Magic Squares
思路
根据题目要求先用函数处理好每一个状态,运用\(set\)判重,因为每一层循环的操作都会是所有的操作数\(+1\),每一次弹出时判断一下是不是最终状态即可
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
using namespace std;
const int N=10;
struct node{
string xu;
int num;
};
string mb;
string yuan="a12345678";
queue<string> q;
map<string,string> vis;//判断函数,判断这一种情况有没有走过
string A(string x)
{
string pd=x;
swap(x[1],x[8]);
swap(x[2],x[7]);
swap(x[3],x[6]);
swap(x[4],x[5]);
if(!vis.count(x))//没有这种情况没有走过,就放进去继续搜索
{
vis[x]=vis[pd]+'A';
q.push(x);
}
return x;
}
string B(string x)
{
string pd=x;
char up=x[4];
char down=x[5];
x[4]=x[3];
x[5]=x[6];
x[3]=x[2];
x[6]=x[7];
x[2]=x[1];
x[7]=x[8];
x[1]=up;
x[8]=down;
if(!vis.count(x))
{
vis[x]=vis[pd]+'B';
q.push(x);
}
return x;
}
string C(string x)
{
string pd=x;
char three=x[3];
char six=x[6];
char seven=x[7];
char two=x[2];
x[6]=three;
x[7]=six;
x[2]=seven;
x[3]=two;
if(!vis.count(x))
{
vis[x]=vis[pd]+'C';
q.push(x);
}
return x;
}
void search()
{
q.push(yuan);
vis[yuan]="";
while(!q.empty())
{
string k=q.front();
q.pop();
A(k);
B(k);
C(k);
if(vis.count(mb)!=0)//当出现了最终情况的时候
//这个时候因为每一次队列的头跳出可以看做可行的序列长度+1
//因为每次走的ABC三种情况是一定不同的,所以如果出现了最终情况一定是
//第一次出现的且是操作次数最短的,所以直接输出就行。
{
cout<<vis[mb].size()<<endl;
cout<<vis[mb]<<endl;
return;
}
}
}
int main()
{
mb+="a";
for(int i=1;i<=8;i++)
{
char a;
cin>>a;
mb+=a;
}//将字符串的位置调到1-8便于操作
search();
return 0;
}
LOJ10028.Knight Moves
思路
存一下每一次走的步数,然后直接广搜就完事了,遇到最终情况就返回后输出,注意判重和打标记,因为是多次询问,别忘了清空堆里面存的值和 \(vis\) 的值
代码
#include<iostream>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<vector>
using namespace std;
const int N=409;
struct node{
int x;
int y;
};
queue<node> q;
int dx[8]={-1,-2,-2,-1,1,2,2,1};
int dy[8]={-2,-1,1,2,2,1,-1,-2};
int vis[N][N];
int step[N][N];
int n,L;
int strx,stry,endx,endy;
void search()
{
q.push((node){strx,stry});
vis[strx][stry]=1;
step[strx][stry]=0;
while(!q.empty())
{
node cun=q.front();
q.pop();
int x=cun.x;
int y=cun.y;
for(int i=0;i<8;i++)
{
int kx=x+dx[i];
int ky=y+dy[i];
if(kx>=0&&kx<L&&ky>=0&&ky<L&&!vis[kx][ky])
{//如果没有找过,判断是否为最终状态
if(kx==endx&&ky==endy)
{
cout<<step[x][y]+1<<endl;
return;
}
else
{
q.push((node){kx,ky});
step[kx][ky]=step[x][y]+1;
vis[kx][ky]=1;
}
}
}
}
}
int main()
{
cin>>n;
while(n--)
{
while(!q.empty()) q.pop();//十年OI一场空,队列不空见祖宗
memset(vis,0,sizeof(vis));
//memset(step,0,sizeof(step));
cin>>L;
cin>>strx>>stry;
cin>>endx>>endy;
if(strx==endx&&stry==endy)
{
cout<<0<<endl;
continue;
}
else search();
}
return 0;
}
P4289 [HAOI2008]移动玩具
思路
对于已经匹配好的点,直接赋值为 \(0\) 不用再去管他,对于没有匹配的点,就继续是原先的位置,再找一个数组存一下没有匹配的位置,以及与所有可以进行匹配的点之间的曼哈顿距离,然后用常规的深搜(乱入)比较一下最小值即可
代码
#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<stack>
#include<map>
#include<vector>
using namespace std;
struct node {
int x,y;
} c[26];//未匹配点的坐标
int a[5][5];
int b[5][5];
int pipe[5][5][5][5];
//前两个下标表示未匹配点坐标
//后两个下标为待匹配点坐标
//存储未匹配点到待匹配点的最短距离
bool used[5][5];
int sum=0;
int minn=0x7fffffff;
void search(int x,int now)
{ //深搜找答案。
if(x>sum)
{
minn=min(minn,now);
return ;
}
for(int i=1; i<=4; i++)
for(int j=1; j<=4; j++)
if(pipe[c[x].x][c[x].y][i][j]!=0&&!used[i][j])//枚举所有没有匹配的点
{
used[i][j]=1;
search(x+1,now+pipe[c[x].x][c[x].y][i][j]);
used[i][j]=0;
}
}
int main() {
char pu;
for(int i=1; i<=4; i++)
for(int j=1; j<=4; j++)
{
cin>>pu;
a[i][j]=pu-'0';
}
for(int i=1; i<=4; i++)
for(int j=1; j<=4; j++)
{
cin>>pu;
b[i][j]=pu-'0';
if(a[i][j]==b[i][j]) a[i][j]=b[i][j]=0;
}//如果已经匹配好了,那么就直接赋值为0,不用管了
for(int i=1; i<=4; i++)
for(int j=1; j<=4; j++)
if(a[i][j]==1)
{
c[++sum].x=i;
c[sum].y=j;
for(int k=1; k<=4; k++)
for(int l=1; l<=4; l++)
if(b[k][l]==1)
{
pipe[i][j][k][l]=abs(i-k)+abs(j-l);
//记录一下从不匹配点到另外的不匹配点的最短距离
}
}
search(1,0);
printf("%d",minn);
return 0;
}
P6909 [ICPC2015 WF]Keyboarding
坑点提醒
1、最后需要你手动添加一个\(“*”\)
2、注意在其实位置按键的情况
思路
首先我们可以把所有相关涉及到的字符都映射为数字,方便处理,包括\((A…Z),(1…9),("*""-")\)这些东东,处理的时候别映射 \(0\) 就行
当我们输入的时候直接映射到一个二维数组 \(a\) 来装载键盘的每个键
将目标串映射到一个一维数组 \(b\) 中来需要按得每一个键,最后别忘了手动添加一位来存 \("*"\)
那么如何选择位置呢,如果在搜索的过程中暴力美剧会非常耗时间,那么这个时候就可以预处理每一个键第一个到达的位置即可
为了方便我们要设一个结构体类型的三维数组
结构体的变量分别为 到达的位置的横坐标 \(x\) ,到达位置的纵坐标 \(y\) ,到达这个位置的时候要匹配目标串的哪一位 \(step\) ,以及到这个点已经走了几步 \(dis\)
三维数组 \(f[k][i][j]\) 表示从 \((i,j)\)向 \(k\) 方向走到达的第一个位置的信息(即结构体中的内容)
那么预处理之后就开始广搜了
在开始广搜之前,因为我们是从第一位开始的,那么我们最好先预处理一下在第一位一共可以连续按几次,然后再把结构体重所表示的数据存进去,在代码中有就不详细说了
那么现在开始广搜
考虑两种情况,我们要把选择和匹配分开找
一、
那么先是匹配,要看一下堆顶的这个位置是否和当前要匹配的位置相同,如果是相同的时候,那就在进行判断一下这个是不是最终状态,如果是,那直接把答案赋值为当前的步数+1就是最有的答案
那如果不是最后的一个情况呢
可以设一个 \(vis\) 数组,用来存当前堆顶的点接下来该匹配哪一位了,然后把相应的结构体中的数据都放进去继续查找
二、
最后再开始选择
首先枚举该点四个位置的到达点,当然因为我在预处理位置的时候有一个位置得移动忘加了,所以还要加上,一次移动
首先判断一下是否越界
然后再判断一下当前要选择的地方要比原坐标的要处理的位置要更大,那么就不需要更新 \(vis\) 数组。
否则的话,就更新当前的 \(vis\) 数组为现在要处理的位置(不用+1,因为是选择,即跳过当前的匹配直接跳进),然后把当前要选择的位置的数据放进去
最后直接输出即可
代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
using namespace std;
const int N=59;
const int M=10009;
struct node{
int x;
int y;
int step;//指下一个到了第几个位置,
int dis;//按了几次
}f[5][N][N];//后两个表示坐标,前一个表示方向,即i,j在k方向上到的最近的点
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
char wor[M];
int n,m,len,a[N][N],b[M],vis[N][N];
map<char,int> mp;
void prepare()//预先将所有的字符串处理成数字类型的,方便处理
{
for(int i=0;i<=9;i++)
mp[(char)('0'+i)]=i+1;
for(int i=0;i<26;i++)
mp[(char)('A'+i)]=i+11;//1-10被取过了
mp['-']=37;
mp['*']=38;
}
void get()//处理一下四个方向可以到达的点
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=0;k<4;k++)
{
int x=i;
int y=j;
while(a[x][y]==a[x+dx[k]][y+dy[k]])//如果一致的话
{
x+=dx[k];
y+=dy[k];//一致加到不同为止
}
f[k][i][j]=(node){x,y,0,0};
}
}
int search()
{
memset(vis,0,sizeof(vis));
queue<node> q;
int ans=0;
int k=1;
while(a[1][1]==b[k]&&k<=len) ++k;//在起点选取的情况
q.push((node){1,1,k,k-1});
vis[1][1]=k;
while(!q.empty())
{
node cun=q.front();
q.pop();
int x=cun.x;
int y=cun.y;
int now=cun.step;
if(a[x][y]==b[now])//此时相等了
{
if(now==len)
{
ans=cun.dis+1;
break;//如果找完了?直接跳出
}
vis[x][y]=now+1;//更新一下,因为按了一遍键盘
q.push((node){x,y,now+1,cun.dis+1});
}
for(int i=0;i<4;i++)
{
node chose=f[i][x][y];
chose.x+=dx[i];
chose.y+=dy[i];//预处理上在加一次,保证完整
if(chose.x<1||chose.x>n||chose.y<1||chose.y>m) continue;//越界了
if(vis[chose.x][chose.y]>=now) continue;//如果选择的地方的要处理的位置比现在的要大,不用改,防止变worse
vis[chose.x][chose.y]=now;//如果可行,那么把接下来要弄得位置给存进去
q.push((node){chose.x,chose.y,now,cun.dis+1});//跳到那一步
}
}
return ans;
}
int main()
{
prepare();
while(scanf("%d",&n)!=EOF)//UVA经典输入方式
{
// cin>>n;
cin>>m;
for(int i=1;i<=n;i++)//输入每一行的字符串
{
cin>>wor;
for(int j=0;j<m;j++)
a[i][j+1]=mp[wor[j]];//将每个字符的代表的数字映射到a数组中
//从1开始方便
}
cin>>wor;
len=strlen(wor);
for(int i=0;i<len;i++) b[i+1]=mp[wor[i]];//依旧是映射,从1开始
len++;
b[len]=38;//注意最后有一个*
get();
cout<<search()<<endl;
}
return 0;
}
P3456 [POI2007]GRZ-Ridges and Valleys
思路
运用广搜板子去做即可,每一次搜索的时候都把找到的高度相同的点标记一下,避免进入死循环,一开始进入搜索的时候先把山峰山谷的值设为 \(1\),然后根据后续的判断,把不匹配的都设为 \(0\) ,最后加上即可
代码
//#include<iostream>
//#include<cstdio>
//#include<cstring>
//#include<algorithm>
//#include<cmath>
//#include<map>
//#include<queue>
//#include<stack>
//#include<set>
//#define int long long
//using namespace std;
//const int N=1009;
//set<int> s;
//int dx[8]={-1,0,1,0,-1,-1,1,1};
//int dy[8]={0,1,0,-1,-1,1,1,-1};
//int n;
//int mount[N][N];
//bool vis[N][N];
//int feng;
//int gu;
//int big(int x,int y)//找比他小的
//{
// int ret=0;
// for(int i=0;i<8;i++)
// {
// int kx=x+dx[i];
// int ky=y+dy[i];
// if(kx>=1&&kx<=n&&ky>=1&&ky<=n&&mount[x][y]>=mount[kx][ky])
// //如果大于周围的,不用判断周围的是不是合法,方便判断
// ret++;
// }
// return ret;
//}
//int small(int x,int y)//找比他大的
//{
// int ret=0;
// for(int i=0;i<8;i++)
// {
// int kx=x+dx[i];
// int ky=y+dy[i];
// if(kx>=1&&kx<=n&&ky>=1&&ky<=n&&mount[x][y]<=mount[kx][ky])
// //如果小于周围的,不用判断周围的是不是合法,方便判断
// ret++;
// }
// return ret;
//}
//int cha(int x,int y){
//
// int ret=0;
// for(int i=0;i<8;i++)
// {
// int kx=x+dx[i];
// int ky=y+dy[i];
// if(kx>=1&&kx<=n&&ky>=1&&ky<=n&&mount[x][y]==mount[kx][ky])
// //如果小于周围的,不用判断周围的是不是合法,方便判断
// ret++;
// }
// return ret;
//
//}
//bool check_big(int x,int y,int num,int lastx,int lasty)//检查大的
//{
// //cout<<"zsf ak ioi"<<endl;
// vis[x][y]=1;
// int pd=0;//用来判断符合的有多少
// int cnt=0;//用来判断一共需要符合条件的是多少
// for(int i=0;i<8;i++)
// {
// int kx=x+dx[i];
// int ky=y+dy[i];
// if(kx<=0||kx>n||ky<=0||ky>n) continue;
// if(vis[kx][ky]) continue;//别向回找
// if(mount[kx][ky]==num)//如果是相等的值
// {
// cnt++;
// if(small(kx,ky)-cha(kx,ky)==0)//如果周围没有比他大的
// {
// if(check_big(kx,ky,num,x,y)) pd++;
// else return false;
// }
// else return false;
// }
// }
// if(pd==cnt) return true;
//}
//bool check_small(int x,int y,int num,int lastx,int lasty)//检查小的
//{
// //cout<<"zsf ak ioi"<<endl;
// vis[x][y]=1;
// int pd=0;//用来判断符合的有多少
// int cnt=0;//用来判断一共需要符合条件的是多少
// for(int i=0;i<8;i++)
// {
// int kx=x+dx[i];
// int ky=y+dy[i];
// if(kx<=0||kx>n||ky<=0||ky>n) continue;
// if(vis[kx][ky]) continue;//别向回找
// if(mount[kx][ky]==num)//如果是相等的值
// {
// cnt++;
// if(big(kx,ky)-cha(kx,ky)==0)//如果周围没有比他小的
// {
// if(check_small(kx,ky,num,x,y))pd++;
// else return false;
// }
// else return false;
// }
// }
// if(pd==cnt) return true;
//}
//void search()
//{
// for(int i=1;i<=n;i++)
// for(int j=1;j<=n;j++)
// {
// if(vis[i][j]) continue;
// int jian=cha(i,j);//减掉相等的
// int lar=big(i,j)-jian;
// int simp=small(i,j)-jian;
// //cout<<"i= "<<i<<" j= "<<j<<" 比他大的有"<<simp<<"个"<<" 比他小的有"<<lar<<"个"<<" 和他一样的有"<<jian<<"个"<<endl;
// if(jian==8)//如果周围都是相同的,那就直接忽略掉,留到以后找
// continue;
// else if(lar==0)//如果没有比他小的
// {
// if(check_small(i,j,mount[i][j],i,j))
// gu++;
// }
// else if(simp==0)//如果没有比他大的
// {
// if(check_big(i,j,mount[i][j],i,j))
// feng++;
// }
// }
//}
//signed main()
//{
// cin>>n;
// for(int i=1;i<=n;i++)
// for(int j=1;j<=n;j++)
// scanf("%lld",&mount[i][j]),s.insert(mount[i][j]);
// if(s.size()==1)
// {
// cout<<1<<" "<<1<<endl;
// return 0;
// }
// search();
// cout<<feng<<" "<<gu<<endl;
// return 0;
//}
/* 暴力深搜,luogu 100pts,LOJ 62pts,好像是爆栈了=_=*/
/* 广搜大法好!!!*/
#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#include<cmath>
#include<stack>
using namespace std;
const int N = 1000 + 10;
int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
struct node
{
int x, y;
};
//结构体存点
int n, big, small;
int map[N][N], vis[N][N];
bool sg, sf;
queue<node> q;
void search(int x, int y)
{
node start; //初始值
start.x=x, start.y=y;
vis[x][y]=1;
q.push(start);
while (!q.empty())
{
node cun=q.front();
q.pop(); //队首
for (int i=0;i<=7;i++)
{//八方向
int nx=cun.x+dx[i];
int ny=cun.y+dy[i];
if (nx<1||nx>n||ny<1||ny>n) continue; //越界
if (map[nx][ny] == map[cun.x][cun.y]&&!vis[nx][ny])
{ // 高度相等打上标记接着搜
vis[nx][ny] = 1;
q.push((node){nx, ny});
}
else
{ // 山峰山谷是否成立
if (map[nx][ny] > map[cun.x][cun.y]) sf = 0;
if (map[nx][ny] < map[cun.x][cun.y]) sg = 0;
}
}
}
}
int main() {
scanf("%d",&n);
bool flag=0;//判断一下是不是所有的都相等
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{
scanf("%d", &map[i][j]);
if (map[i][j]!=map[1][1]) flag=1;
}
if (!flag)
{ // 判断是否全部高度相等
cout<<1<<" "<<1<<endl;
return 0;
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{
if (!vis[i][j])
{ //如果是新的联通块
sf=1,sg=1;
search(i,j);
big+=sf;
small+=sg;
}
}
printf("%d %d\n", big, small);
return 0;
}
结语
当做一个题目的时候,尽量先有爆搜的思路稳住分数,把暴力分拿满之后再去思考正解,稳重求进。