BFS

  |  

$0 状态转移的代价(边权)与队列类型的关系

只计最少步数,每步没有代价

  • 用普通 BFS,$O(N)$
    每个状态访问一次(入队一次)

记最少代价,每一步(每次扩展)的代价可能是 0 或 1

  • 双端队列 BFS,$O(N)$
    每个状态更新多次(入队多次),扩展一次,第一次出队时为该状态的最小代价

每次扩展的代价是任意数值(等价于一般的最短路)

  • 优先级队列BFS,$O(N\log N)$,类似 dijkstra
    每个状态更新多次,扩展一次,第一次出队时为该状态最小代价

  • 迭代+普通BFS,$O(N^{2})$,类似 spfa
    每个状态更新,扩展多次(入队,出队多次)


$1 BFS 可解决的问题

1.1 组合搜索和隐式图搜索

组合搜索,隐式图搜索

1.2 树搜索

树的BFS遍历

1.3 图搜索

最短路径

连通分量

二分图判定

拓扑排序

1.4 图搜索的 BFS 模板(无权图最短路径为例)

BFS 求 st 到 ed 的最短路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int bfs(const vector<vector<int>>& g, int st, int ed)
{
vector<int> d(n, -1);
vector<int> pre(n, -1); // 若需要保存路径
queue<int> q;
d[st] = 0;
q.push(st);
int ans = 0;
while(!q.empty())
{
int cur = q.front();
q.pop();
for(int son: g[cur])
{
if(d[son] != -1) continue;
d[son] = d[cur] + 1;
pre[son] = cur;
if(son == ed)
{
ans = 1;
break;
}
q.push(son);
}
}
if(!ans) return ans;
// 输出最短路
stack<int> sta;
int u = ed;
while(u != -1)
{
sta.push(u);
u = pre[u];
}
while(!sta.empty())
{
sta.pop();
}
return -1;
}

双向 BFS 求 st 到 ed 的最短路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 双向 BFS
int bibfs(const vector<vector<int>>& g, int st, int ed)
{
vector<int> d(n, 0); // 0 代表未访问, +-号代表颜色(起点色,终点色)
queue<int> q;
d[st] = 1;
d[ed] = -1;
q.push(st);
q.push(ed);
while(!q.empty())
{
int cur = q.front();
q.pop();
for(int son: g[cur])
{
if(d[son] == 0) // 未访问过
{
d[son] = (d[cur] > 0 ? d[cur] + 1 : d[cur] - 1);
q.push(son);
}
else
{
if((d[cur] ^ d[son]) < 0) // 异号,颜色不同
return abs(d[cur]) + abs(d[son]);
}
}
}
return -1;
}

$2 经常配合使用的方法

2.1 无权图最短路径(BFS 基本模型)

1311. 获取你好友已观看的视频

  • 建图
  • 边权为 1

909. 蛇梯棋

  • 建图
  • 边权为 1
  • 定义队列中的状态结构体

2039. 网络空闲的时刻

  • 建图
  • 边权为 1

2.2 复杂状态表示和状态转移

定义状态结构体,以及用合适的数据结构维护状态

365. 水壶问题

317. 离建筑物最近的距离

  • 边权为 1
  • 多源BFS
  • 染色
  • 队列中的 Point
    1
    2
    3
    4
    5
    6
    7
    struct Point
    {
    int i, j;
    int c; // 颜色
    int d; // 与 c 的源的距离
    Point(int i, int j, int c, int d):i(i),j(j),c(c),d(d){}
    };
  • 各个位置的状态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct State
    {
    vector<int> d; // d[c] := 到颜色为 c 的建筑物的距离, -1 表示未触达
    int cnt; // 已经触达的建筑数目
    int sum; // 已经触达的建筑的距离和
    State(int cnt=0)
    {
    sum = 0;
    this -> cnt = 0;
    d.assign(cnt, -1);
    }
    };

1197. 进击的骑士

  • 队列中的 State
    1
    2
    3
    4
    5
    6
    7
    struct State
    {
    int x;
    int y;
    int d;
    State(int x, int y, int d):x(x),y(y),d(d){}
    };
  • 维护已经访问过的状态
    • unordered_set<State>
    • vector<vector<bool>>
  • 力扣1197-进击的骑士

1210. 穿过迷宫的最少移动次数

  • 队列中的 State
    1
    2
    3
    4
    5
    6
    7
    struct State
    {
    int x, y; // 蛇尾位置
    int ori; // 蛇的方向,0: 横, 1: 竖
    int d;
    State(int x, int y, bool ori, int d):x(x),y(y),ori(ori),d(d){}
    };

1293. 网格中的最短路径

  • 边权为 1
  • 队列中的 State
    1
    2
    3
    4
    5
    6
    7
    struct State
    {
    int i, j;
    int k; // 剩余的删除次数
    int d;
    State(int i, int j, int k, int d):i(i),j(j),k(k),d(d){}
    };
  • visited 需要对 i, j, k 均做记录

2.3 状态压缩

864. 获取所有钥匙的最短路径

1284. 转化为全零矩阵的最少反转次数

  • 将01矩阵(棋盘状态)压缩为整数
  • 队列中的状态
    1
    2
    3
    4
    5
    6
    struct State
    {
    int s; // 状态压缩后的棋盘状态
    int d;
    State(int s, int d):s(s),d(d){}
    };
  • 哈希集合维护已经访问过的棋盘状态

2.4 双端队列(01边权模型)

deque 相关设计模式:工作秘取模式

在生产者-消费者模型中,有一个队列统一管理任务,多个 Worker 从一个队列中取任务执行、执行完再去队列取任务。

在工作密取模式中,每个 Worker 自带一个 deque,里面有该 Worder 待执行的任务,如果一个 Worker 的 deque 里的任务执行完了,它会找到其它未完成人物的 Worker,然后秘密地将该 Worker 的 deque 的尾部弹出,加入到自己的 deque 中。这是用到双端队列的一种设计模式。

1368. 使网格图至少有一条有效路径的最小代价

1263. 推箱子

状态比较复杂,取决于两个点

2.5 优先级队列(带权最短路模型)

778. 水位上升的泳池中游泳

407. 接雨水 II

2.6 随机队列

随机队列相当于一个包(Bag),是一堆无序元素。随机队列BFS 可以用于生成迷宫

随机 dequeue 的实现:随机抽取数组一个元素,和队尾元素交换,将队尾元素出队

另:随机队列可以用于实现蓄水池抽样

迷宫生成

蓄水池抽样

2.7 多源BFS

286. 墙与门

542. 01 矩阵

417. 太平洋大西洋水流问题

  • 多源BFS
  • 染色

1162. 地图分析

1765. 地图中的最高点

1020. 飞地的数量

2.8 双向BFS

127 单词接龙

面试题 17.22. 单词转换

  • 没说要最短,直接 DFS 即可无需 BFS

752. 打开转盘锁

  • 不建图,双向BFS(边稠密,收益极大)

433. 最小基因变化

  • 不建图,双向BFS

2.9 有限BFS

1036. 逃离大迷宫

2.10 常数优化

675. 为高尔夫比赛砍树

2.11 两次搜索

126. 单词接龙 II

934. 最短的桥

  • BFS/DFS+BFS
  • 第一次搜索标记第一个岛的位置,和第二个岛的位置(可以 BFS 或 DFS)
  • 第二次搜索从第二个岛开始 BFS

2.12 染色

913. 猫和老鼠

2.13 Astar

1091. 二进制矩阵中的最短路径

力扣1091-Astar,IDAstar

675. 为高尔夫比赛砍树

854. 相似度为 K 的字符串

【搜索难题】力扣854-相似度为K的字符串

773. 滑动谜题

2.14 随机化

模拟退火

1515. 服务中心的最佳位置

  • 模拟退火

力扣1515-服务中心的最佳位置


$3 经典问题

3.1 Flood Fill

994. 腐烂的橘子

面试题 08.10. 颜色填充

200. 岛屿数量

  • BFS
  • Follow up : 二维离散化

130. 被围绕的区域

1020. 飞地的数量

733. 图像渲染

827. 最大人工岛

529. 扫雷游戏

3.2 迷宫

490. 迷宫

505. 迷宫 II

499. 迷宫 III

1391. 检查网格中是否存在有效路径

迷宫图上的连通图问题,也可以DFS,从比较难的插头DP来的

1210. 穿过迷宫的最少移动次数

LCP 13. 寻宝


Share