【算法笔记】8.2 广度优先搜索BFS
原创
©著作权归作者所有:来自51CTO博客作者云端FFF的原创作品,请联系作者获取转载授权,否则将追究法律责任
文章目录
一、引子
- 还用上一篇DFS中的迷宫例子引入,参考【算法笔记】8.1 深度优先搜索DFS
- 这次我们改变寻路策略,不知要找出路线,而且想知道从起点到终点的最短步数是多少(两个相邻节点间看作相隔一步),我们可以按如下的图示进行宽度优先搜索
- 起点A是第一层,发现从A出发能访问到B和C,于是B、C是第二层
- 按顺序访问第二层,先看B。从B出发能访问到D和E,于是D、E是第三层,等第二层访问完要按顺序访问
- 继续访问第二层的C,从C出发能访问到F和G,于是F、G是第三层,等第二层访问完要按顺序访问,而且它们排在D、E之后
- 第二层访问完毕,开始访问第三层的第一个结点D,第四层加入H、I、J
- 继续访问第三层的E,第四层加入K、L、M
- 继续访问第三层的F,发现F是死胡同,不管他
- 访问第三层最后一个点G,发现G是出口,算法结束,后面第四层的结点可以不管了
- 按照上述分析过程,可以看出层数就是从A出发到达相应点的步数,所有从A到G至少要3步
二、广度优先搜索
1. 定义
- 广度优先搜索算法(Breadth-First Search,BFS)是一种盲目搜寻法,它的思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止
- 广度优先搜索让你能够找出两样东西之间的最短距离,不过最短距离的含义有很多!使用广度优先搜索可以:
- 编写国际跳棋AI,计算最少走多少步就可获胜;
- 编写拼写检查器,计算最少编辑多少个地方就可将错拼的单词改成正确的单词,如将READED改为READER需要编辑一个地方;
- 根据你的人际关系网络找到关系最近的医生
注:上面一段出自《算法图解》
关键点有两个:
- BFS适用于找最短距离
- 最短距离可以被包装成很多样子出现在不同的题目中
2. 用队列实现BFS
- 回到引子给出的地图问题,可以看出:BFS的过程很适合用队列实现
- A入队
- A出队,把A能访问的B、C入队
- B出队,把B能访问的D、E入队
- …
- 因此BFS算法常使用队列实现,且总是按照层次的顺序进行遍历
- BFS常用模板如下
void BFS(int s)
{
queue<int> q;
q.push(s)
while(!q.empty())
{
取出队首元素top
(访问队首元素top)
队首元素出队
将top的下一层节点中未曾入队的元素全部入队,并设置为已入队
}
}
- BFS的辅助队列常用STL的
queue
- 为了防止有节点重复入队,一般要给每个节点做一个是否入队过的标记。注意是是否入过队而不是是否访问过!
- 当对队列中元素不只是访问,而且要做修改时,STL的
queue
不能直接修改,所以最好开个大数组,在队列中存数组下标
三、相关例题
1. 块的个数
- 给出一个
M x N
的矩阵,其中元素为0或1,每个元素和它上下左右四个元素是相邻的。如果矩阵中有若干各1是相邻的(不必两两相邻),就称这些1构成了一个 “块”。求给定矩阵中 “块” 的个数 - 思路:遍历矩阵中每一个元素,如果为0就跳过,如果为1,就用BFS查询和它相邻的四个位置看有没有为1的元素(第一层),如果有就再检查这个块相邻的四个元素(第二层),直到整个块访问完毕。为了防止重复检查,可以设一个数组记录每个位置是否已经被检查过
- 示例代码
/* 块的个数 —— BFS */
#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;
const int maxN = 100;
typedef struct NODE
{
int x;
int y;
}NODE;
int n, m; // 矩阵大小
int matrix[maxN][maxN]; // 01矩阵
bool inq[maxN][maxN] = { false }; // 记录元素是否已经入过队
int X[4] = { 0,0,1,-1 }; // 增量数组
int Y[4] = { 1,-1,0,0 };
//判断(x,y)是否需要访问
bool judge(int x, int y)
{
if (x >= n || x < 0 || y >= n || y < 0) //越界了
return false;
if (matrix[x][y] == 0 || inq[x][y] == true) //当前位置是空的或者已经入过队了
return false;
return true;
}
//BFS访问(x,y)元素所在的块,给此块中所有1做标记
void BFS(int x, int y)
{
queue<NODE> q; //辅助队列
NODE thisNode; //根结点
thisNode.x = x;
thisNode.y = y;
q.push(thisNode);
inq[x][y] = 1;
while (!q.empty())
{
//队首出队
NODE top = q.front();
q.pop();
//检查队首的相邻点,为1就入队并打标记
for (int i = 0; i < 4; i++)
{
int newX = top.x + X[i];
int newY = top.y + Y[i];
if (judge(newX, newY))
{
thisNode.x = newX;
thisNode.y = newY;
q.push(thisNode);
inq[newX][newY] = 1;
}
}
}
}
int main()
{
//输入矩阵
scanf("%d%d", &n, &m);
for (int x = 0; x < n; x++)
for (int y = 0; y < m; y++)
scanf("%d", &matrix[x][y]);
//遍历矩阵,一但有1出现,块计数+1,同时用DFS把块中点都标记上
int ans = 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); //把此块的所有1的inq标记为true
}
}
}
printf("%d\n", ans);
system("pause");
return 0;
}
/* 样例输入
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 1 1 0 0
1 1 1 1 0 0 0
*/
- 这个题也可以用DFS递归实现,直接套DFS模板,修改上面的BFS函数如下即可,其他都不用变
void DFS(int x,int y)
{
//边界判断
if (!judge(x, y))
return;
//当前点是块中点,先标记,然后横向遍历解答树所有子节点进行DFS
inq[x][y] = 1;
for (int i = 0; i < 4; i++)
{
int newX = x + X[i];
int newY = y + Y[i];
if (judge(newX, newY))
DFS(newX,newY);
}
}