今天接着搞广度优先搜索。
昨天的DFS是一条路走到底,而BFS就不一样了,BFS是一层层的来,就想步兵排雷一样,从一个点向前方辐射延伸,一层层的探测,探测到的没有地雷地方列入安全区,然后下一步站在安全区上进行探测。
BFS使用具有先入先出特性的队列来实现(和DFS恰恰反着)。
基本逻辑如下:(该逻辑模型引用自http://blog.youkuaiyun.com/sallyxyl1993/article/details/57077512)
初始化:
起点值初始化(past=NULL,dist=0,visit=1)
其他节点值初始化(past=NULL,dist=无穷,visit=0)
起点入队
循环1:直到队列中没有元素
从队伍中输出一个节点作为当前节点
循环2:访问与当前节点连通但是【没有被访问过】的节点(visit=0的节点)
将即将访问的节点记为正在访问的状态
将即将访问的节点的状态更新(past=当前节点,dist=即将访问的节点到当前节点的距离,visit=1)
即将访问的节点入队
将当前节点的visit记为2(因为与它连接的所有节点都被访问过)
仍然是上昨天的例题:解救小哈(BFS做法)
小哈去玩迷宫,结果迷路了,小哼去救小哈。迷宫由n行m列的单元格组成(n和m都小于等于50),每个单元格要么是空地,要么是障碍物。
问题:帮小哼找到一条从迷宫的起点通往小哈所在位置的最短路径。(注意:障碍物不能走,小哼也不能走出迷宫外)
输入例如:
0 0 1 0
0 0 0 0
0 0 1 0
0 1 0 0
0 0 0 1
1 1 4 3
输出:
7
分析:
思路:通过一层一层拓展方法来找到小哈,像扫雷一样,拓展时每发现一个可以前往的点就将这个点加入到队列中,直至走到小哈的位置(p,q)时为止。
第一步:建立数据结构模拟队列,进行各种初始化,起点加入队列,做好准备工作
struct note {
int x;
int y;
int s;
};
struct note que[2501];
int head,tail;
int a[51][51]={0};//用来存储地图
int book[51][51]={0};//作用是记录哪些点已经队列中,防止一个点被重复拓展,全部初始化为0
//最开始需要进行初始化,即将队列设置为空
head=1;
tail=1;
que[tail].x=1;
que[tail].y=1;
que[tail].s=0;
tail++;
book[1][1]=1;
}
第二歩,确定一个点的限制条件。
满足加入队列条件的点:
- 不能是棋盘外的,即不能越界
- 不能是已经走过的点
- 不能是障碍物
即
//判断是否越界
if(tx<1 || tx>n || ty<1 || ty>m)//这里使用不满足条件进行下一个队列点的扫描会使代码比较简洁易懂
continue;
//判断是否为障碍物 或者已经在路径中
if(a[tx][ty]==0 && book[tx][ty]==0) {
}
这个就是一个点加入队列的条件。
如果满足条件,我们就把这个点称为安全点并加入队列,即:
book[tx][ty]=1; //把这个点标记为走过,注意宽搜每个点只入列一次,所以和深搜不同,不需要将book数组还原
//插入新的点到队列中
que[tail].x=tx;
que[tail].y=ty;
que[tail].s=que[head].s+1;//步数是父亲步数+1
tail++;
当我们把这个点可以拓展的点全部入列的时候,这个起始点就可以出列了,
实现:head++;
另外关于如何对一个点的各个方向进行扩展的小技巧:
建立向量数组:
int next[4][2]={
{0,1},//向右
{1,0},//向下
{0,-1},//向左
{-1,0}};//向上
我可以这样搞出来下个点的坐标:
for(int k=0;k<=3;k++) {
//计算下一点坐标
tx=que[head].x+next[k][0];
ty=que[head].y+next[k][1];
完整的代码:
#include<cstdio>
#include<iostream>
using namespace std;
struct note {
int x;
int y;
int f;//父亲在队列中的编号,本体不要求输出路径,可以不需要f
int s;
};
int main() {
struct note que[2501];
int a[51][51]={0},book[51][51]={0};
//定义一个用于表示走的方向的数组
int next[4][2]={
{0,1},//向右
{1,0},//向下
{0,-1},//向左
{-1,0}};//向上
int k,n,m,tx,ty,flag;
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
int startx,starty,p,q;
cin>>startx>>starty>>p>>q;
//队列初始化
int head,tail;
head=1;
tail=1;
//往队列插入迷宫入口坐标
que[tail].x=startx;
que[tail].y=starty;
que[tail].f=0;
que[tail].s=0;
tail++;
book[startx][starty]=1;
flag=0;//用来标记是否到达目标点,0表示暂时没有到达,1表示到达
while(head<tail) { //当队列不空时候循环
for(int k=0;k<=3;k++) {
//计算下一点坐标
tx=que[head].x+next[k][0];
ty=que[head].y+next[k][1];
//判断是否越界
if(tx<1 || tx>n || ty<1 || ty>m)
continue;
//判断是否为障碍物 或者已经在路径中
if(a[tx][ty]==0 && book[tx][ty]==0) {
book[tx][ty]=1; //把这个点标记为走过,注意宽搜每个点只入列一次,所以和深搜不同,不需要将book数组还原
//插入新的点到队列中
que[tail].x=tx;
que[tail].y=ty;
que[tail].f=head;//因为这个点是从head拓展而来,所以它的父亲是head,本题目不需要求路径,因此本句可以省略
que[tail].s=que[head].s+1;
tail++;
}
if(tx==p &&ty==q) {
flag=1;
break;
}
}
if(flag==1)
break;
head++;//此地方不能忘记,当一个点拓展结束,head++才能对后面的点再进行拓展
}
//打印队列中末尾最后一个点(目标点)的步数
//注意tail是指向队列队尾(即最后一位)的下一个位置,所以这需要-1
printf("%d",que[tail-1].s);
getchar();getchar();
return 0;
}
嗯,先写到这吧,感觉书上的这个例题理解起来特别好,等明天有空再改一下昨天的日志,昨天的DFS在这例题这写的不够详细(准确说是太偷懒了)。另外差不多是时候做题了。哈哈哈有点期待呢。
那就先到这吧,
#include<iostream>
using namespace std;
int main()
{
cout<<"Good night,world~"<<endl;
}