BFS是最简单的图搜索算法之一,也是很多复杂算法的基础。最简单的模型是:给你一个图和一个起点,你从该点出发,搜索与之相邻的点,然后再搜索邻接点的邻接点,直到找到目标
图有很多变种,无向图和有向图对于BFS其实都差不多,读取对应的邻接信息就好了,如果是带权图,BFS要启用优先队列来代替普通的队列
BFS需要队列来存储找到的节点,队列先进先出的原则保证了先找到的点先展开
除此之外,开始的点也不一定是一个,可能多个点同时开始,也可能是起点和终点的双向BFS(比如迷宫中两人相遇问题)
但实际上,能用BFS解决的问题不仅仅是图搜索,图和节点往往是隐藏的,抽象的——实际问题可能看上去和图上查找对应节点毫不相关,但将其内在的逻辑关系理清后,发现完全可以用BFS来做
BFS还经常用于洪泛问题——从一个点出发,按临界距离的远近一步一步为周遭的节点打上标签。这样的洪泛不仅仅在图上进行,还可以是坐标组成的网格——它们具有天然的邻居关系
BFS算法应用时,复杂度一般就是考虑节点数量和每个节点连接的边数,或是抽象节点的数量
在例题中,除了抽象图之外,还可能会出现更复杂的情况——如推箱子问题,箱子每走一步都意味着人的一次BFS,实际上就是一个双层的BFS
由于BFS也常常作为其他算法的基础,我们也会看到BFS仅仅用于查询当前可到达节点,真正的算法决策需要其他条件来配合的情况
POJ3278_Catch The Cow
描述
Farmer John的一头牛逃跑了,现在FJ在位置N,牛在位置K,FJ可以花费一分钟做以下三种操作:(假设现在FJ的位置是cur)
1 移动到cur + 1。
2 移动到cur - 1。
3 传送到cur × 2。
求FJ到达K点的最短时间。
思路
经典中的经典,BFS,当前大一刚刚入队就做过这道题。之所以再做是因为算法课的作业。其实我一直搞不懂为什么算法课要分成回溯法、分支限界法。不都是搜索 + 剪枝么
好像回溯法是DFS,分支限界法是BFS来着?
这题刚拿到的时候想用DP来做,错了。复盘之后才意识到,dp的顺序必须按BFS序来展开。其实也能做。我是直接按照端点从小到大做的,可能会重复更新。比如我拿着3去更新了2,4,和6,然后遇到4时,可能又会更新3,导致2,6,可能又有了更优的解,但我更新不到
所以,想用DP的话,其实还是要实现BFS,这题直接BFS的算法对一个节点也不会访问第二次,所以没必要
用BFS的经典模型去套的话,本题是一个单向图,每个数字是一个点,即所有可能到达的位置是一个点,起点是N,终点是K,邻居关系是任何一个点cur和cur + 1,cur - 1,以及cur << 1有单向邻居关系,本题的难点就在这里,将题目要求抽象为一个有向图,只要能想到这一层,接下来的BFS就没什么难度了
解决
1 |
|
POJ3322_Bloxorz I
描述
一个游戏,控制一个底面为1×1正方形,高为2的长方体在二维网格里翻转行进。长方体可以以它的任意一个面着地,并且永远保持贴近网格的一面的边与网格的边线重合。
所谓翻转行进,就是长方体以它贴近网格的一面的某条棱为轴,沿着把这个面升起来的方向旋转九十度。
网格分为三种,空网格意味着长方体的任何一部分都不能位于它的上面,易碎网格只能支撑长方体一半的重量,这意味着长方体不能以底面朝下的方式立在易碎网格上。普通网格可以支撑长方体的重量,长方体可以以任意一面立在普通网格上。
游戏初始时,长方体会处于某个特定的位置,游戏胜利的方式是将长方体翻转到目标点。
保证目标点是单点,这意味着翻转到目标点时,必然是长方体的一个底面贴在网格上。
思路
网格具有天然的邻接关系,可以看做抽象的图,但本题还需要注意的是长方体的状态,即使是在同一个网格,长方体不同的面朝下状态也不一样。理论上长方体有6个面朝下这6种状态,由于对称性可以简化为3种,侧面朝下时,只需要记录靠左和靠下的一格即可
所以实际上,每个网格对应三个抽象的节点,每个抽象的节点是一个二元组——(坐标,状态),然后根据状态转移的规则(即长方体翻转的方式)节点之间有了邻接关系
根据易碎网格,我们可以进行剪枝
解决
1 |
|
CH2501_矩阵距离
描述
给定一个N行M列的01矩阵 A,A[i][j] 与 A[k][l] 之间的曼哈顿距离定义为:
dist(A[i][j],A[k][l])=|i-k|+|j-l|
输出一个N行M列的整数矩阵B,其中:
B[i][j]=min(1≤x≤N,1≤y≤M,A[x][y]=1){dist(A[i][j],A[x][y])}
即求与每个位置曼哈顿距离最近的1
N,M≤1000
思路
BFS,从每个值为1的点出发,每次向周边蔓延一个单位。这个题就是之前提过的洪泛法
被称为flood-fill,即从源头开始倒水,蔓延到整个图
解决
1 |
|
POJ1475_Pushing Boxes
描述
求解推箱子游戏,要求求出箱子移动次数最少的解,并输出推箱子的路径。如果有多种方式可以让箱子移动最少,输出人物移动最少的那组
数据范围
地图不大于20 * 20
思路
下意识的思路是箱子的位置(二元组)和人的位置(二元组)设置成四元组,但由于过于复杂,会出错
其实这里我没有估计准确——160000个状态节点,每个节点邻接4个状态——再加上碰壁检测可以剪枝,总计感觉是百万级别的计算量,但就是错了
照蓝书上写了一个双重BFS的做法:先外层以箱子的位置为状态进行第一层BFS,每次状态转移时,需要使用第二层BFS来搜索人物移动的最短路径
这题麻烦的一点还在于要输出路径,还需要重新进行搜索,再来一次双重的BFS
所以这个题很难——一般不会想到这种双层BFS的
解决
1 |
|
CH2601_电路维修
描述
Ha’nyu是来自异世界的魔女,她在漫无目的地四处漂流的时候,遇到了善良的少女Rika,从而被收留在地球上。Rika的家里有一辆飞行车。有一天飞行车的电路板突然出现了故障,导致无法启动
电路板的整体结构是一个R行C列的网格(R,C≤500),如下图所示。每个格点都是电线的接点,每个格子都包含一个电子元件。电子元件的主要部分是一个可旋转的、连接一条对角线上的两个接点的短电缆。在旋转之后,它就可以连接另一条对角线的两个接点。电路板左上角的接点接入直流电源,右下角的接点接入飞行车的发动装置
思路
从某个点到另一个点,如果电路联通,相当于有一条权值为0的路,不连通视为权值为1,在次基础上进行BFS找出最短路。权值为0的路的处理方法是:直接从队头入队
这样就需要一个双端队列
还需要考虑的问题是,我们需要维护某个点到起点的最小距离,因为一个点可能会被访问很多次
解决
1 |
|
POJ3635_Full Tank?
描述
有n个城市之间通过m条路相连,每条路都有一个权值,表示汽车从该路的一端到达另一端需要消耗的汽油数。每个城市都有加油站,并且不同的加油站可能油价不相同
现在,给出一些询问,要求计算出最小的花销
每条询问包含油箱的容量c,起始城市s,终点城市e
如果无解输出impossible
数据范围
c不大于100,城市数目不大于1000,道路数目不大于10000,每个城市的油价不大于100
思路
BFS其实只是这题的基础,单纯的BFS其实是解决不了这种带权值的问题的,将二元组(city, oil)作为状态,使用优先队列来取出当前所有可达状态中花费最小的。然后,对于每个状态,可以有两种选择:一是在油箱没满的情况下,可以就地加油;二是在油箱里的油足够的情况下,去相邻的城市
直到终点城市第一次从队列中取出,BFS结束
无解的判断可以在求解开始之前,先进行一个BFS连通性判断
仅有上面的框架还不够,本题需要进行优化:记录某个状态下的最小花费,如果取出某个状态时发现其花费大于之前某个状态的最小花费,则舍弃,否则更新并继续遍历
解决
1 |
|
HDOJ3085_Nightmare II
描述
从前有一个抽象怪叫erriyue,他做了一个噩梦,梦里他和女友被困在迷宫的不同位置,有两个鬼在抓他们,他们要赶在被鬼抓住之前汇合
erriyue每秒可以移动3个曼哈顿距离,女友只能移动一个(气抖冷),鬼会向所有距离鬼现在位置曼哈顿距离小于等于2的地方分裂,无视墙壁,直到整个迷宫的每个格都有一个鬼
每一秒,鬼会先行动
思路
双向BFS,以秒为单位,找出每秒erriyue和女友可以到达的位置,直到erriyue或者女友发现自己可以到达一个对方到过的位置为止
注意判断BFS新拓展出的节点是否可达时,应考虑越界,离鬼太近,撞墙和已经来过这些情况
由于我一开始没看到鬼会先行动,所以绕了弯子,可达的新节点在下一秒仍有可能被鬼抓到,所以在取出新节点时又加入了一个是否会被抓到的判断
最后交上去只过了66.7%的测试点,仔细检查发现是一不小心输出了调试信息……
解决
1 |
|