关于宽搜BFS广度优先搜索的那点事
以前一直知道深搜是一个递归栈,广搜是队列,FIFO先进先出LILO后进后出啥的。DFS是以深度作为第一关键词,即当碰到岔道口时总是先选择其中的一条岔路前进,而不管其他岔路,直到碰到死胡同时才返回岔道口并选择其他岔路。接下来将介绍的广度优先搜索(Breadth First Search, BFS)则是以广度为第一关键词,当碰到岔道口时,总是先一次访问从该岔道口能直接到达的所有节结点,然后再按这些结点被访问的顺序去依次访问它们能直接到达的所有结点,以此类推,直到所有结点都被访问为止。这就跟平静的水面中投入一颗小石子一样,水花总是以石子落水处为中心,并以同心圆的方式向外扩散至整个水面,从这点来看BFS和DFS那种沿着一条线前进的思路是完全不同的。
广度优先搜索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)继续循环。
例1:给出一个mxn的矩阵,矩阵中的元素为0或1。称位置(x,y)与其上下左右四个位置(x,y+1)、(x,y-1)、(x+1,y)、(x-1,y)是相邻的。如果矩阵中有若干个1是相邻的(不必两两相邻),那么称这些1构成了一个“块”。求给定的矩阵中“块”的个数。
【输入样例】
6 7
0 1 1 1 0 0 1
0 0 1 0 0 0 0
0 0 0 0 1 0 0
0 0 0 1 1 1 0
1 1 1 0 1 0 0
1 1 1 1 0 0 0
【输出样例】
4
【解题思路】
求解的基本思想是:枚举每一个位置的元素,如果为0,则跳过;如果为1,则使用BFS查询与该位置相邻的4个位置(前提是不出界),判段它们是否为1(如果某个相邻的位置为1,则同样去查询与该位置相邻的4个位置,直到整个“1”块访问完毕)。而为了防止走回头路,一般可以设置一个bool型数组inq来记录每个位置是否在BFS中已入过队。
对当前位置(x,y)来说,由于与其相邻的四个位置分别为(x,y+1)、(x,y-1)、(x+1,y)、(x-1,y),那么不妨设置两个增量数组,来表示四个方向。
int X[4] = {0,0,1,-1};
int Y[4] = {1,-1,0,0};
这样就可以使用for循环来枚举4个方向,以确定与当前坐标(nowX, nowY)相邻的4个位置,如下所示:
for(int i = 0;i<4;i++)
{
newX = nowX + X[i];
newY = nowY + Y[i];
}
#include <bits/stdc++.h> #include <queue> using namespace std; const int maxn = 100; struct node { int x,y; //位置(x,y) }Node; int n,m; //矩阵大小为n*m int matrix[maxn][maxn]; //01矩阵 bool inq[maxn][maxn]; //记录位置(x,y)是否已入过队 int X[4] = {0,0,1,-1}; //增量数组 int Y[4] = {1,-1,0,0}; bool judge(int x,int y) //判段坐标(x,y)是否需要访问 { //越界返回false if(x>=n || x<0 || y>=m || y<0) return false; //当前位置为0,或(x,y)已入过队,返回false if(matrix[x][y] == 0 || inq[x][y] == true) return false; //以上都不满足,说明需要访问,返回true return true; } //BFS函数方位位置(x,y)所在的块,将该块中所有“1”的inq都设置为true void bfs(int x, int y) { queue<node>Q; //定义队列 Node.x = x, Node.y = y; //当前结点的坐标为(x,y) Q.push(Node); //将结点Node入队 inq[x][y] = true; //设置(x,y)已入过队 while(!Q.empty()) { node top = Q.front(); //取出队首元素 Q.pop(); //队首元素出队 for(int i = 0;i<4;i++) //循环4次,得到4个相邻位置 { int newX = top.x + X[i]; int newY = top.y + Y[i]; if(judge(newX,newY)) //如果新位置(newX,newY)需要访问 { //设置Node的坐标为(newX,newY) Node.x = newX, Node.y = newY; Q.push(Node); //将结点Node加入队列 inq[newX][newY] = true; //设置位置(newX,newY)已入过队 } } } } int main() { freopen("1.txt","r",stdin); //从文件读入 cin>>n>>m; for(int x = 0;x<n;x++) for(int y = 0;y<m;y++) cin>>matrix[x][y]; //读入01矩阵 int ans = 0; //存放块数 for(int x= 0;x<n;x++) //枚举每一个位置 for(int y = 0;y<m;y++) //如果元素为1,且未入过队 if(matrix[x][y] == 1 && inq[x][y] == false) { ans++; //块数加1 bfs(x,y); //访问整个块,将该块所有“1”的inq都标记为true } cout<<ans<<endl; return 0; }
例2
5 5
.....
.*.*.
.*S*.
.***.
...T*
2 2 4 3
#include <bits/stdc++.h> using namespace std; const int maxn = 100; struct node { int x,y; //位置(x,y) int step;//step为从起点S到达该位置的最少步数(即层数) }S,T,Node; //S为起点,T为终点,Node为临时结点 int n,m; //n行m列 char maze[maxn][maxn]; //迷宫信息,字符数组 bool inq[maxn][maxn] = {false}; //记录(x,y)是否已入过队 int X[4] = {0,0,1,-1}; //增量数组 int Y[4] = {1,-1,0,0}; //检测位置(x,y)是否有效 bool test(int x,int y) { if(x>=n || x < 0 || y >= m || y < 0) return false; //超过边界 if(maze[x][y] == '*') return false; //墙壁* if(inq[x][y] == true) return false; //已入过队 return true; //有效位置 } int BFS() { 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++) //循环4次,得到4个相邻位置 { int newX = top.x + X[i]; int newY = top.y + Y[i]; if(test(newX,newY)) //位置(newX,newY)有效 { Node.x = newX; Node.y = newY; Node.step = top.step + 1; //Node层数为top层数加1 q.push(Node); //将结点Node加入队列 inq[newX][newY] = true; //设置位置(newX,newY)已入过队 } } } return -1; //无法到达终点T时返回-1 } int main() { freopen("2.txt","r",stdin); cin>>n>>m; for(int i = 0;i<n;i++) { for(int j = 0;j<m;j++) cin>>maze[i][j]; } cin>>S.x>>S.y>>T.x>>T.y;//起点和终点的坐标 S.step = 0; //初始化的起点的层数为0,S到T的最少步数 cout<<BFS()<<endl; return 0; }
例3
#include <bits/stdc++.h> using namespace std; struct node { int x,y,z; }Node; int n,m,L,T; //矩阵为n*m,共有L层,T为1的个数的下限 int pixel[1290][130][61]; //三位01矩阵 bool inq [1290][130][61] = {false}; int X[6] = {0,0,0,0,1,-1}; //增量矩阵 int Y[6] = {0,0,1,-1,0,0}; int Z[6] = {1,-1,0,0,0,0}; bool judge(int x,int y,int z) { if(x>=n||x<0||y>=m||y<0||z>=L||z<0) return false; if(pixel[x][y][z] == 0 || inq[x][y][z] == true) return false; return true; } int BFS(int x,int y,int z) { int tot = 0; //计数当前块中1的个数 queue<node> Q; //定义队列 Node.x = x,Node.y = y,Node.z = z; Q.push(Node); //将结点Node入队 inq[x][y][z] = true; //设置位置(x,y,z)已入过队 while(!Q.empty()) { node top = Q.front(); //取出队首元素 Q.pop(); tot++; for(int i = 0;i<6;i++) { int newX = top.x + X[i]; int newY = top.y + Y[i]; int newZ = top.z + Z[i]; if(judge(newX, newY,newZ)) { Node.x = newX,Node.y=newY,Node.z = newZ; Q.push(Node); inq[newX][newY][newZ]= true; } } } if(tot>=T) return tot; else return 0; } int main() { freopen("3.txt","r",stdin); cin>>n>>m>>L>>T; for(int z = 0;z<L;z++) for(int x = 0;x <n;x++) for(int y = 0;y<m;y++) cin>>pixel[x][y][z]; int ans = 0; //记录卒中核心区1的个数总和 for(int z =0;z<L;z++) for(int x = 0;x<n;x++) for(int y = 0;y<m;y++) if(pixel[x][y][z] == 1 && inq[x][y][z] == false) { ans += BFS(x,y,z); } printf("%d\n",ans); return 0; }