- 这是《算法笔记》的读书记录
- 本文参考自8.2节
一、引子
-
还用上一篇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
不能直接修改,所以最好开个大数组,在队列中存数组下标
- BFS的辅助队列常用STL的
三、相关例题
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); } }