4.3 BFS

4.3 BFS

紫书那里我们当时是先学习的二叉树的BFS,再学的简单图的BFS,最后往更深入的学习回溯,路劲寻找问题和A*算法,IDA*算法,黑书这里将这些汇总了起来放在一章节进行介绍(只是希望不要像上上章那么坑就好)。


4.3.1 BFS和队列

首先要知道BFS(广度优先搜索)和DFS(深度优先搜索)都是最基本的两大暴力搜索技术,常用于解决最简单的图和树的遍历问题。那么如何区分和理解两种算法呢?我们以老鼠走迷宫为例,有两种不同的方法:

1.一只老鼠走迷宫。它在每个路口都选择先走某个固定的方向,直到碰壁无法继续向前走,然后再回退一步,往另外的方向走(这个方向我们一般用一个数组存储),直到找到出口(目标点)。我们用这个方法能够走遍所有的路(注意是多次),且不会重复,这个方法就是DFS。

2.一群(无限多的)老鼠走迷宫,这群老鼠进去后,在每个路口派出部分老鼠(也是无限的)探索所有没走过的路,走某条路的老鼠,如果碰壁无法前行,就停下;如果到达的路口已经有其他老鼠探索过了,也停下,直到找到目标。

由于所有老鼠的速度都一样,这群老鼠中第一个到达出口的老鼠走的路径一定是最短路径,这个方法就是BFS。

求一条可行路径或求出所有路径,我们往往使用DFS,求出满足某种规则的最优路径,我们往往使用BFS。在具体编程中,一般用队列来实现BFS,用递归或栈来实现DFS,事实上一般用递归比较容易理解,用栈我也是在学习了数据结构之后才知道有这种处理方法。

接下来来看黑书上的例题:

hdu 1312 “Red and Black”

有一个长方形的房间,铺着方形瓷砖,瓷砖为红色或黑色,一个人站在黑色瓷砖上,他可以按上下左右方向移动到相邻的瓷砖。但他不移动到红色瓷砖。请你设计一个算法计算出他可以到达的黑色瓷砖数目。

给定长宽包含的瓷砖数,"·“表示黑色瓷砖,”#“表示红色瓷砖,”@"表示人所在的位置。

分析:这个问题书上说和老鼠走迷宫差不多(其实我个人觉得和连通块更类似),就是以“@”所在的方块作为起点,不断地向外“扩散”直到无法扩散为止。

由于从我的角度已经不是第一次做BFS的问题了,那我们就直接一边看代码一边分析这个问题了。

int dir[4][2]={
   {
   -1,0},{
   0,-1},{
   1,0},{
   0,1}},num;//分别表示向左向上向右向下横纵坐标的变化
struct node{
   int x,y};//node以方格作为结点,dx和dy表示起点的横纵坐标 
void BFS(int dx,int dy){
   num=1; queue<node>q; node start,next;//q即是BFS队列的主体,start为队首,next为邻域 
	start.x=dx; start.y=dy; q.push(start);//将起点,即头结点放入队列 
	while (!q.empty()){
   start=q.front(); q.pop();//每次出队首结点进行扩展
		//然后将扩展出来的结点加入到队列的末尾,扩展出来的新结点next从start变化得到 
		for (int i=0;i<4;i++){
   next.x=start.x+dir[i][0]; next.y=start.y+dir[i][1];
		//扩展出来的新结点需要是可以到达的结点,即对应的字符是"·",还需要不越界,check即用来检查越界	
			if (check(next.x,next.y)&&room[next.x][next.y]=='.'){
   
				room[next.x][next.y]='#'; num++;//进队之后更改符号,标记未处理过防止重复遍历
				//注意防止重复遍历是常规操作,但直接从原图进行更改需要根据题目要求确定能不能这么做 
				q.push(next); 
			} 
		}
	} 
} 

就不多说了,BFS我个人觉得比DFS要复杂一点,思路和老鼠走迷宫的类似,但是具体的代码需要自己先几遍好好体会。

接下来是习题。

poj 3278 “Catch That Cow”

一个老头想要抓牛,告诉你老头和牛的位置,老头每分钟可以向当前的位置+1,-1,或者乘以2(瞬移),牛牛的位置不动(就等死),问最快多少分钟之内可以抓到这头牛。

分析:我还是第一次知道还有线段上面的BFS:+1,-1,乘2分别是三种结点上的决策,由于需要打印步数,结点除了位置的属性还需要步数的属性。直接看代码吧:

#include<queue>
#include<iostream>
using namespace std;
struct node{
   int step,x;};//x表示位置,step表示步数 
int xx[100001]={
   0};//xx[i]用于标记位置i是否已经到达过
//start,next,q和模板里面的一样,大于等于0小于等于100000即是结点位置属性的范围 
int bfs(int n,int k){
   queue<node>q; node start,next; start.step=0; start.x=n; q.push(start);
	while (!q.empty()){
   start=q.front(); q.pop();
		if (start.x==k) return start.step; next.step=start.step+1; xx[start.x]=1;
		next.x=start.x+1; if (next.x<=100000&&!xx[next.x]) {
   xx[next.x]=1; q.push(next);}
		next.x=start.x-1; if (next.x>=0&&!xx[next.x]) {
   xx[next.x]=1; q.push(next);}
		next.x=start.x*2; if (next.x<=100000&&!xx[next.x]) {
   xx[next.x]=1; q.push(next);}
	}
} 
int main(){
   int n,k; cin>>n>>k; cout<<bfs(n,k); return 0; 
}

主要就是标记和越界那里需要注意一下不要忘记了。

poj 1426 “Find The Multiple”

给出一个整数n。求出任意一个它的倍数m,要求m必须只由十进制的0或1组成。

分析:m的数据范围到100,这显然暴力是肯定不太行的(结果我超时了去搜博客,发现暴力是可以过的,甚至直接long long都行)。然后这道题样例给的不是当前情况的最小值(我以为是我错了,然后我跑了别人正确的代码发现别人正确代码运行的结果和我的一样hh)。

我的思路是类似数位dp,每一次在当前状态对应的二进制数后面添加“0”或“1”,以除以n的余数作为d值(可以说和数位dp火柴的那题非常类似了),当除以n的余数为0时BFS结束。

#include<queue>
#include<cstring>
#include<iostream>
using namespace std;
int vis[200];//vis[i]表示除以n余数为i的结果是否已经出现过 
struct node{
   string s; int x;};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值