• 这是《算法笔记》的读书记录
  • 本文参考自8.2节

文章目录

  • ​​一、引子​​
  • ​​二、广度优先搜索​​
  • ​​1. 定义​​
  • ​​2. 用队列实现BFS​​
  • ​​三、相关例题​​
  • ​​1. 块的个数​​

一、引子

  • 还用上一篇DFS中的迷宫例子引入,参考【算法笔记】8.1 深度优先搜索DFS
  • 这次我们改变寻路策略,不知要找出路线,而且想知道从起点到终点的最短步数是多少(两个相邻节点间看作相隔一步),我们可以按如下的图示进行宽度优先搜索
  1. 起点A是第一层,发现从A出发能访问到B和C,于是B、C是第二层
  2. 按顺序访问第二层,先看B。从B出发能访问到D和E,于是D、E是第三层,等第二层访问完要按顺序访问
  3. 继续访问第二层的C,从C出发能访问到F和G,于是F、G是第三层,等第二层访问完要按顺序访问,而且它们排在D、E之后
  4. 第二层访问完毕,开始访问第三层的第一个结点D,第四层加入H、I、J
  5. 继续访问第三层的E,第四层加入K、L、M
  6. 继续访问第三层的F,发现F是死胡同,不管他
  7. 访问第三层最后一个点G,发现G是出口,算法结束,后面第四层的结点可以不管了
  • 按照上述分析过程,可以看出层数就是从A出发到达相应点的步数,所有从A到G至少要3步

二、广度优先搜索

1. 定义

  • 广度优先搜索算法(Breadth-First Search,BFS)是一种盲目搜寻法,它的思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止
  • 广度优先搜索让你能够找出两样东西之间的最短距离,不过最短距离的含义有很多!使用广度优先搜索可以:
  • 编写国际跳棋AI,计算最少走多少步就可获胜;
  • 编写拼写检查器,计算最少编辑多少个地方就可将错拼的单词改成正确的单词,如将READED改为READER需要编辑一个地方;
  • 根据你的人际关系网络找到关系最近的医生

注:上面一段出自《算法图解》
关键点有两个:

  1. BFS适用于找最短距离
  2. 最短距离可以被包装成很多样子出现在不同的题目中

2. 用队列实现BFS

  • 回到引子给出的地图问题,可以看出:BFS的过程很适合用队列实现
  1. A入队
  2. A出队,把A能访问的B、C入队
  3. 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);
}

}