即BFS
BFS 的核心思想就是把一些问题抽象成图,从一个点开始,向四周开始扩散。一般来说,我们写 BFS 算法都是用「队列」这种数据结构,每次将一个节点周围的所有节点加入队列。
BFS 相对 DFS 的最主要的区别是:BFS 找到的路径一定是最短的,但代价就是空间复杂度比 DFS 大很多。
框架
BFS 出现的常见场景:让你在一幅「图」中找到从起点start
到终点target
的最近距离,这个例子听起来很枯燥,但是 BFS 算法问题其实都是在干这个事儿。
这个广义的描述可以有各种变体,比如走迷宫,有的格子是围墙不能走,从起点到终点的最短距离是多少?如果这个迷宫带「传送门」可以瞬间传送呢?再比如说两个单词,要求你通过某些替换,把其中一个变成另一个,每次只能替换一个字符,最少要替换几次?
本质上就是一幅「图」,让你从一个起点,走到终点,问最短路径。这就是 BFS 的本质。
// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
Queue<Node> q; // 核心数据结构
Set<Node> visited; // 避免走回头路
q.offer(start); // 将起点加入队列
visited.add(start);
int step = 0; // 记录扩散的步数
while (q not empty) {
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0; i < sz; i++) {
Node cur = q.poll();
/* 划重点:这里判断是否到达终点 */
if (cur is target)
return step;
/* 将 cur 的相邻节点加入队列 */
for (Node x : cur.adj())
if (x not in visited) {
q.offer(x);
visited.add(x);
}
}
/* 划重点:更新步数在这里 */
step++;
}
}
bfs的本质是在“图”中的扩散式搜索,每一步都会把当前队列中的元素 的 周围的元素都收入队列,从搜索的大框架来说,从最开始只往队列中加入一个元素起,每一步的操作都是依次把队列中的元素弹出(返回并删除该元素),对该元素进行比对(若是target则返回),若不是target则把这个元素的相邻元素全部压入队列之中(事实上并不是全部,为了避免重复操作,必须在这一步把已操作过的元素略过)。
一般采用队列进行装载,采用hashset来记录是否访问过。
正如上面所说,bfs解决的是从起点到终点的最短路径,那么使用bfs前一定要先想明白起点和终点是什么。
实例1:
leet111.二叉树的最小深度
本题很简单,起点是root,终点是叶子结点。
叶子节点的判断条件就是左右节点都是null,这里还有个点就是二叉树是不能走回头路的,也就是说不会出现已访问过的元素,那么就无需设置visited。
class Solution {
public int minDepth(TreeNode root) {
if(root==null){return 0;}
int step=1;
Queue<TreeNode> q=new LinkedList<TreeNode>();
q.offer(root);
while(!q.isEmpty()){
int sz=q.size();
for(int i=0;i<sz;i++){
TreeNode curr=q.poll();
if(curr.left==null&&curr.right==null){
return step;
}
if(curr.left!=null){
q.offer(curr.left);
}
if(curr.right!=null){
q.offer(curr.right);
}
}
step++;
}
return -1;
}
}
本题就是按照框架来。
bfs用在二叉树上,那就是每次搜索收入一层的节点,然后依次比对。
实例2:
leet752.打开转盘锁
本题多了一个deadends,但也可以用一个hashset去收集,然后比对,问题不大。
找相邻的元素,也就是往上/往下旋转按钮,可以通过char[] 与 string的变换来操作。
其余的还是用框架。
class Solution {
public int openLock(String[] deadends, String target) {
Queue<String> q=new LinkedList<String>();
Set<String> dead=new HashSet<>();
for(String s:deadends){dead.add(s);}
Set<String> visited=new HashSet<>();
q.offer("0000");
visited.add("0000");
int step=0;
while(!q.isEmpty()){
int sz=q.size();
for(int i=0;i<sz;i++){
String curr=q.poll();
if(dead.contains(curr)){
continue;
}
if(curr.equals(target)){
return step;
}
for(int j=0;j<4;j++){
if(!visited.contains(up(curr,j))){
q.offer(up(curr,j));
visited.add(up(curr,j));
}
if(!visited.contains(down(curr,j))){
q.offer(down(curr,j));
visited.add(down(curr,j));
}
}
}
step++;
}
return -1;
}
String up(String s,int j){
char[] t=s.toCharArray();
if(t[j]=='9'){t[j]='0';}
else{
t[j]+=1;
}
return new String(t);
}
String down(String s,int j){
char[] t=s.toCharArray();
if(t[j]=='0'){t[j]='9';}
else{
t[j]-=1;
}
return new String(t);
}
}
BFS 的逻辑,depth
每增加一次,队列中的所有节点都向前迈一步,这保证了第一次到达终点的时候,走的步数是最少的。而DFS 实际上是靠递归的堆栈记录走过的路径,你要找到最短路径,肯定得把二叉树中所有树杈都探索完才能对比出最短的路径有多长。BFS 借助队列做到一次一步「齐头并进」,是可以在不遍历完整棵树的条件下找到最短距离的。形象点说,DFS 是线,BFS 是面;DFS 是单打独斗,BFS 是集体行动。BFS 可以找到最短距离,但是空间复杂度高,而 DFS 的空间复杂度较低。