广度优先搜索 BFS 学习笔记

引入

广搜是图论中的基础算法之一,属于一种盲目搜寻方法。

广搜需要使用队列来实现,分以下几步:

  1. 将起点插入队尾;
  2. 取队首 \(u\),如果 $u\to v $ 有一条路径,则将 \(v\) 插入队尾;
  3. 如果队列不为空,重复执行 \(2\sim 3\) 步。

如上图,就是一次 BFS 的搜索过程。利用 BFS,我们可以在 \(O(n+m)\) 的时间内对一张图实现遍历,其中 \(n\) 为点数,\(m\) 为边数。

代码实现:

void bfs(int s) {
q.push(s);
d[s] = 0;
while (q.empty() == false) {
int u = q.front();
q.pop();
if (vis[u]) continue;
vis[u] = true;
for (int i = hd[u]; i; i = nxt[i]) {
int v = to[i];
d[v] = d[u] + 1;
q.push(v);
}
}
}

其中,\(\text{hd, nxt, to}\) 均为邻接表中的数组。如果你不会什么是邻接表,那么建议先学习图论基本知识后再来看本篇文章。

应用

在 BFS 的过程中,我们求出一个 \(d\) 数组,\(d_i\) 表示起点到 \(i\) 的最短路径,也称为 \(i\) 的层级。我们发现,在 BFS 的过程中,相当于是一层一层的向外扩展。这就会带来一个很好的性质:当 \(v\) 被第一次访问,\(v\) 的最短路径就已经确定。也就是说,之后的搜索不可能搜到一个比之前更短的路径了

BFS 过程中,队列具有单调性。也就是说,队列呈现这个样子:

例题1 走迷宫

给出 \(n\times n \;(n\le1000)\) 的 0-1 矩阵,\(1\) 表示不能通过,\(0\) 表示可以通过,问起点 \((sx,sy)\) 到终点 \((ex,ey)\) 至少需要走多少步。

BFS 模板题。从 \((sx,sy)\) 开始 BFS,第一次搜寻到 \((ex,ey)\) 的时候就必定是 \((sx,sy) \to (ex,sy)\) 的最短路。给出代码。注意,这里有一个小 trick,可以使用定义两个偏移量数组 \(\text{dx, dy}\) 来寻找从 \((x,y)\) 能推到的地方,详情看代码。

const int MAXN = 1005;

int n, sx, sy, ex, ey, a[MAXN][MAXN];
int dx[] = { 1, 0, -1, 0 };
int dy[] = { 0, 1, 0, -1 };
bool vis[MAXN][MAXN]; struct NODE {
int x, y, t;
}; queue<NODE> q; void bfs() {
q.push((NODE){ sx, sy, 0 });
while (q.empty() == false) {
NODE p = q.front();
if (p.x == ex && p.y == ey) {
cout << p.t;
return;
}
for (int i = 0; i < 4; ++i) {
int tx = p.x + dx[i], ty = p.y + dy[i];
if (a[tx][ty] == 0 && vis[tx][ty] == false) {
q.push((NODE){ tx, ty, p.t + 1 });
vis[tx][ty] = true;
}
}
q.pop();
}
} int main(void) {
memset(vis, true, sizeof vis);
cin >> n;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
char ch;
cin >> ch;
a[i][j] = (ch - '0' ? 1 : 0);
vis[i][j] = false;
}
}
cin >> sx >> sy >> ex >> ey;
bfs();
return 0;
}

这种问题称作 Flood-Fill,是 BFS 最基本的应用。

例题2 山峰山谷

给定一个 \(n\times n\;(2\le n\le 1000)\) 的网格状地图,每个方格 \((i,j)\) 有一个高度 \(w_{i,j}\;(0\le w_{i,j} \le 10^9)\)。如果两个方格有公共顶点,则它们是相邻的。

定义山峰山谷如下:均由地图上的一个联通块组成。所有方格高度都相同。周围的方格(即不属于山峰或山谷但与山峰或山谷相邻的格子)高度均大于山谷的高度,或小于山峰的高度。

求地图内山峰和山谷的数量。特别的,如果整个地图方格的高度均相同,则整个地图即是一个山谷,也是一个山峰。

考虑 BFS。对于每个点,若未被访问,则从这个点开始 BFS。

对于每一个通过 \(u\) 搜索到的点 \(v\),若 \(v\) 的权值与 \(w\) 的权值相同,那么就将 \(v\) 插入队尾。否则,判断如果 \(v\) 比 \(u\) 小,那么当前这个连通块只可能是山峰,否则只可能是山谷。若搜索到的这个连通块既有比他高的、也有比他矮的,那么他啥也不是。搜索后统计即可。

代码:

const int way[8][2] = {
{ -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 }, { -1, -1 }, { -1, 1 }, { 1, -1 }, { 1, 1 }
};
int n, a[1010][1010], v[1010][1010], q[1000100][2], h, t, maxn, minn, ans_max, ans_min; bool check(int x, int y) { return (x > 0 && x <= n && y > 0 && y <= n); } void BFS(int x, int y) {
maxn = minn = h = t = 0;
q[++t][0] = x, q[t][1] = y;
while (h++ < t) {
for (int i = 0; i < 8; i++) {
int xx = q[h][0] + way[i][0];
int yy = q[h][1] + way[i][1];
if (check(xx, yy)) {
if (a[xx][yy] == a[x][y]) {
if (!v[xx][yy]) {
v[xx][yy] = 1;
q[++t][0] = xx, q[t][1] = yy;
}
} else {
if (a[xx][yy] > a[x][y])
maxn++; //统计周围高的
if (a[xx][yy] < a[x][y])
minn++; //统计周围矮的
}
}
}
}
if (!minn)
ans_min++; //周围没有矮的就是山峰
if (!maxn)
ans_max++; //周围没有高的就是山谷
} int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) scanf("%d", &a[i][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (!v[i][j])
BFS(i, j);
printf("%d %d", ans_max, ans_min);
}

例题3 0/1 最短路

给出一张图,其中每条边的权值 \(w\in \{0,1\}\),求起点 \(s\) 到终点 \(e\) 的最短路。要求 \(O(n)\) 求解。

这道题目是一道非常巧妙的题目。我们要是使用原本的 BFS 搜索,就有可能不满足要求的队列单调性。如当搜索以下图的时候,\(5\) 节点由于层级比 \(2\) 深,于是会在 \(2\) 之后入队,但是他的权值又比 \(2\) 小,所以就不满足队列的单调性了。

如何解决这个问题呢?观察到每条路的权值为 \(0\) 或 \(1\),那么也就是说,假设目前搜索到点 \(u\),\(u \to v\) 有一条权值为 \(w\) 的路径,那么如果 \(w=1\),就把 \(v\) 放入队尾,否则放入队头。这样,我们仍能保证队列的单调性。读者可以模拟一下上图 BFS 的过程,感受双端队列 BFS 的巧妙之处。

广度优先搜索 BFS 学习笔记的更多相关文章

  1. 【数据结构与算法Python版学习笔记】图——词梯问题 广度优先搜索 BFS

    词梯Word Ladder问题 要求是相邻两个单词之间差异只能是1个字母,如FOOL变SAGE: FOOL >> POOL >> POLL >> POLE > ...

  2. 深度优先搜索DFS和广度优先搜索BFS简单解析(新手向)

    深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每个点仅被访问一次,这个过程就是图的遍历.图的遍历常用的有深度优先搜索和广度优先搜索,这两者对于有向图和无向图 ...

  3. 利用广度优先搜索(BFS)与深度优先搜索(DFS)实现岛屿个数的问题(java)

    需要说明一点,要成功运行本贴代码,需要重新复制我第一篇随笔<简单的循环队列>代码(版本有更新). 进入今天的主题. 今天这篇文章主要探讨广度优先搜索(BFS)结合队列和深度优先搜索(DFS ...

  4. 深度优先搜索DFS和广度优先搜索BFS简单解析

    转自:https://www.cnblogs.com/FZfangzheng/p/8529132.html 深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每 ...

  5. 平面上的地图搜索--Java学习笔记(四)

    版权声明: 本文由Faye_Zuo发布于http://www.cnblogs.com/zuofeiyi/, 本文可以被全部的转载或者部分使用,但请注明出处. 这一个月以来,都在学习平面上的地图搜索,主 ...

  6. 广度优先搜索(BFS)

    定义 维基百科:https://en.wikipedia.org/wiki/Breadth-first_search 给定图G=(V,E)和一个可识别的源结点s,广度优先搜索对图G中的边进行系统性的探 ...

  7. 数据结构和算法总结(一):广度优先搜索BFS和深度优先搜索DFS

    前言 这几天复习图论算法,觉得BFS和DFS挺重要的,而且应用比较多,故记录一下. 广度优先搜索 有一个有向图如图a 图a 广度优先搜索的策略是: 从起始点开始遍历其邻接的节点,由此向外不断扩散. 1 ...

  8. 深度优先搜索DFS和广度优先搜索BFS

    DFS简介 深度优先搜索,一般会设置一个数组visited记录每个顶点的访问状态,初始状态图中所有顶点均未被访问,从某个未被访问过的顶点开始按照某个原则一直往深处访问,访问的过程中随时更新数组visi ...

  9. (转)广度优先搜索BFS和深度优先搜索DFS

    1. 广度优先搜索介绍 广度优先搜索算法(Breadth First Search),又称为"宽度优先搜索"或"横向优先搜索",简称BFS. 它的思想是:从图中 ...

随机推荐

  1. bzoj2007/luoguP2046 海拔(平面图最小割转对偶图最短路)

    bzoj2007/luoguP2046 海拔(平面图最小割转对偶图最短路) 题目描述: bzoj  luogu 题解时间: 首先考虑海拔待定点的$h$都应该是多少 很明显它们都是$0$或$1$,并且所 ...

  2. JVM知识(一) 求你了,别再说Java对象都是在堆内存上分配空间的了!

    求你了,别再说Java对象都是在堆内存上分配空间的了! https://baijiahao.baidu.com/s?id=1661296872935371634&wfr=spider& ...

  3. abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?

    abstract的method 不可以是static的,因为抽象的方法是要被子类实现的,而static与子类扯不上关系! native方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类 ...

  4. Kafka 的设计架构你知道吗?

    Producer :消息生产者,就是向 kafka broker 发消息的客户端. Consumer :消息消费者,向 kafka broker 取消息的客户端. Topic :可以理解为一个队列,一 ...

  5. synchronized与Lock、volatile的区别

    synchronized与volatile的区别 volatile是线程同步的轻量级实现,因此volatile性能好于synchronized voaltile修饰变量,synchronized修饰方 ...

  6. 打败算法 —— 环形链表 II

    本文参考 出自LeetCode上的题库 -- 环形链表II,哈希表和快慢指针两种解法都需要O(n)的时间,但快慢指针仅占用O(1)的空间 https://leetcode-cn.com/problem ...

  7. Markdown语法2

    二 . 低频使用的语法 下面是相对用得少的markdown语法,但也值得学习学习. 10.区块(块引用) 要创建块引用,请在段落前添加一个 >  符号: 块引用支持多段落: 块引用支持嵌套,即引 ...

  8. carsim2016事件如何设置

    #carsim2016事件设置# 完成以下功能:车速低于60km/h时,加速,设置节气门开度为0.8,制动主斜体样式缸压力设为0:车速高于120km/h时,制动,设置节气门开度为0,制动主缸压力设置为 ...

  9. Altium_Designer PCB文件的绘制(上:PCB基础和布局)

    PCB设计基础知识 PCB面板 在PCB设计中,最重要的一个面板就是"PCB面板".该面板的功能主要是对电路板中的各个对象进行精确定位,并以特定的效果显示出来.该面板还可以对各种对 ...

  10. 该如何选择 background-image 和 img 标签

    用img标签 如果你希望别人打印页面时候包含这张图片请使用 img 标签 当这张图片有非常有意义的语义,比如警告图标,请使用img标签及它的alt属性.这样意味着你可以向所有的用户终端现实他的意义. ...