Breadth First Search(BFS)以广度为第一关键词,当碰到岔路口时,总是先一次访问从该岔路口能直接到达的所有节点,然后再按照这些节点被访问的顺序去一次访问他们能直接到达的所有节点,以此类推,直到所有节点都被访问。
BFS一般由队列实现,且总是按层次的顺序进行遍历,其基本写法如下:
void BFS(int s)
{
queue<int> q;
q.push(s);
while (!q.empty)
{
取出队首元素top;
访问队首元素top;
将队首元素出队;
将top的下一层节点中未曾入队的节点全部入队,并设置为以入队;
}
}
下面是对每一个步骤的说明:
1.定义队列q,并将起点s入队
2.写一个while循环,循环条件是队列q非空
3.在while循环里,先取出队首元素top,然后访问他(访问可以是任何事情,例如输出)。访问完将其出队
4.将top的下一层节点中所有未曾入队的节点入队,并标记他们的层号为now的层号加1,同时设置这些入队的节点已入过队。
5.返回2.继续循环
接下来看两个迷宫类问题:
一.
给出一个mxn的矩阵,矩阵的元素为0或1.称位置(x,y)与其上下左右四个位置(x,y+1),(x,y-1),(x+1,y),(x-1,y)是相邻的。如果矩阵中有若干个1是相邻的(不必凉凉相邻),那么称这些1构成了一个块。求给定的矩阵中块的个数。
0111001
0010000
0000100
0001110
1110100
1111000
例如上面的6x7矩阵中,块的个数是4
需要一个函数在遍历到有1存在时,能找到这个1身边的所有1。这个算法这里采用BFS。
#include<stdio.h>
#include<queue>
using namespace std;
const int MAXN 100;
struct node
{
int x, y;
} Node;
int n, m;//矩阵大小
int matrix[MAXN][MAXN];//矩阵
bool inq[MAXN][MAXN];//判断某个点是否已经入队过
bool judge(int x,int y)//边界判断函数
{
if(x<0||x>=n||y<0||y>=m)//超过边界的情况
return false;
if(inq[x][y]==true||matrix[x][y]==0)//值为0或已经入过队为
return false;
else
return true;
}
void BFS(int x,int y)
{
queue<node> Q;
Node.x = x; Node.y = y;
int X[4] = {0, 0, 1, -1};//方便未来循环使用
int Y[4] = {1, -1, 0, 0};
Q.push(Node);
inq[x][y] = true;
while(!Q.empty)
{
node top = Q.front(); //取出队首元素
Q.pop();//清除队首元素
for (int i = 0; i < 4;i++)
{
int newX = top.x;
int newY = top.y;
if(judge(newX,newY))//下面是一整套循环操作,用新点替换老点
{
Node.x = newX;
Node.y = newY;
Q.push(Node);
inq[newX][newY] = true;
}
}
}
}
int main()
{
//输入整个矩阵
scanf("%d %d", &m, &n);
for (int i = 0; i < n;i++)
{
for (int j = 0; j < m;j++)
{
scanf("%d", matrix[i][j])
}
}
int ans = 0;//1块初始化为0
for (int x = 0; x < n;x++)
{
for (int y = 0; y < m;y++)
{
if(matrix[x][y]==1&&inq[x][y]==false)
{
ans++;
BFS[x][y];
}
}
}
printf("%d", ans);
return 0;
}
可以看到BFS每次遍历值为1的点的周围四个点,找到为1的点再以该点为中心重新遍历周围四个点,循环往复
这一题也可以使用DFS的算法来解,留待以后再写吧。
二.
给定一个nm大小的迷宫,其中代表不可通过的墙壁,而“.”代表平地,S代表起点,T代表终点。移动过程中,如果当前位置是(x,y)(下标从0开始),且每次只能前往上下左右(x,y+1),(x,y-1),(x+1,y),(x-1,y)四个位置的平地,求从起点S到达终点T的最少步数。
. . . . .
. * . * .
. * S * .
. * * * .
. . . T *
在这个样例中,S的坐标为(2,2),T的坐标为(4,3)。
和上一例类似,需要的函数也类似。有哪些边界条件呢?这个边界是指不能正常通过的
1.x,y超出范围
2.遇到障碍物
3.已经碰到过
而BFS的功能还是和之前一样,只是多了一个step的特征值。
#include<stdio.h>
#include<queue>
#include<string.h>
using namespace std;
const in MAXN 100;
struct node
{
int x, y;
int step;
} S,T,Node;
int m, n;//迷宫矩阵行列数
char matrix[MAXN][MAXN];//迷宫矩阵
bool inq[MAXN][MAXN] = {false};
int X[4] = {0,0,1,-1};
int Y[4] = {1,-1,0,0};
bool judge(int x,int y)//边界判断
{
if(x<0||x>=n||y<0||y>m)//超出边界
return false;
if(inq[x][y]==true||matrix[x][y]=='*')//遇到障碍物或者已经被遍历过
return false;
else
return true;
}
int BFS(int x,int y,int step)//其功用就是找到下一层的可以通行的点
{
queue<node> Q;
Q.push(S);//将起点S入队
while(!Q.empty())
{
Node top=Q.front(); //取出队首元素
Q.pop();//踢出队首元素
if(top.x == T.x&&top.y==T.y)
{
return top.step;
}
for (int i = 0; i < 4;i++)//第一件事,遍历四周的点
{
int newX = top.x + X[i];
int newY = top.y + Y[i];
if (judge(newX,newY))
{
Node.x = newX;
Node.y = newY;
Node.step = top.step+1;
q.push(Node);
inq[newX][newY] = true;
}
}
}
return 0;
}
感觉比DFS好理解啊。。还是太菜了
再强调一点,再BFS中设置的inq(in queue)数组的含义是判断节点是否已入过队,而不是节点是否已被访问。区别在于:
如果设置成是否已被访问,有可能在某个节点正在队列中(但还未访问时)由于其他节点可以到达它而将这个节点再次入队,导致很多节点反复入队,计算量大大增加。因此BFS中让每个节点只入队一次,故需要设置inq数组的含义为:节点是否已入过队而非节点是否已被访问。
最后指出,当使用STL的queue时,元素入队的push操作只是制造了该元素的一个副本入队,因此在入队后对原元素的修改不会影响队列中的副本,而对队列在副本的修改也不会改变原元素,需要注意由此可能引入的bug(一般由结构体产生)。