参考资料:广度优先搜索—wikipedia
最短路径问题—wikipedia
目录
广度优先算法
BFS(即广度优先搜索)是图论中一种常见的算法,常用于二叉树数据结构,能够实现对树或图中每个节点的遍历。本文将从leetcode的一些例题中详尽介绍这种算法的实现原理和在实际问题中的应用方式。
一、概念
- 原理:如其名字,BFS是从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法终止。
- 结束条件:树或图中的所有节点都已被遍历。
如果还存在未被遍历的节点,则选择其中一个作为源节点并重复以上过程,整个过程反复进行知道所有节点都被遍历为止。
二、实现方法(利用Queue)
BFS有广泛的应用,这里为了阐述其方法的具体实现流程,暂以寻找目标值为例子,假设我们的目的是:从一个拓扑结构中找到一个节点的值与target相同
- 将根节点放入queue中;
- 从queue中取出第一个节点,并检验它是否为target;
如果是,则结束搜索并回传结果。
如果不是,则将其所有尚未检验果的直接子节点加入queue中。 - 若queue为空,则表示整张图都检查过了,return null。
- 重复步骤2;
这里一定要注意BFS和DFS的区别:
- BFS是一次性把某节点的所有子节点都放到stack中,而DFS是把其中某一个子节点放入stack;
- 由于DFS的遍历顺序与递归时的弹栈很类似,所以在对树进行DFS有时和递归可以结合,则不一定需要用到数据结构queue;
而BFS的遍历顺序会导致丢失根节点而无法回溯,所以需要额外的空间来存储仍需遍历的节点,故一定要用到数据结构queue;
三、时空复杂度
- 时间复杂度:O(V+E),V为节点数,E为边数
最差情况下,BFS必须查找所有到可能节点的所有路径。 - 空间复杂度:O(V+E)
因为所有节点都必须被存储
由于对空间的大量需求,因此BFS不适合解图非常大的问题。
四、应用
BFS的应用很广泛,最有特色的两个应用为:
- 层序遍历
- 最短路径问题(非加权图)
最短路径问题
最短路径问题是图论研究中一个经典的算法问题,旨在寻找图中两节点之间的最短路径,有以下四种形式:
- 确定起点的最短路径问题(单源最短路问题)
边权非负时:Dijkstra算法
边权为负时:Bellman-ford算法或SPFA算法 - 确定终点的最短路径问题
无向图:与确定起点的问题完全等价
有向图:等价于把所有路径方向反转的确定起点的问题 - 确定起点和终点的最短路劲该问题
- 全局最短路径问题(多源短路问题)
求图中所有的最短路径:Floyd-Warshall算法
这本文中只考虑单源最短路径问题,那么针对图的分类又分成了以下几种:
这BFS专题里,我们还不想过深地讨论其他的图论算法,所以我们只关注无权图和有向无环图两种情况,而有向无环图在leetcode刷题之DFS专题中有详细讲解关于拓扑排序和DFS实现。本文中的最短路径问题只涉及无权图的最短路径问题及其变种。
层序遍历
一、二叉树的层序遍历(母题):leetcode—102
-
题目描述
-
分析
层序遍历是BFS最重要应用之一,此题便用BFS解决。
1)corner case分析:root为null
2)创建空queue以存放每层待遍历的TreeNode
3)创建ArrayList result用来存放最终的结果
—————开始循环(以层为单位)——————
4)创建ArrayList level以存放每层的输出结果
5)对每个节点遍历后,将其左右孩子放入queue中
6)该层循环结束后,将level加入到result中
————————结束循环—————————
7)返回result -
代码(此处提供本人代码仅供参考)
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
if(root == null){
return result;
}
Queue<TreeNode> q = new LinkedList<TreeNode>();
q.offer(root);
while(!q.isEmpty()){
List<Integer> level = new ArrayList<Integer>();
int currLevelSize = q.size();
for(int i = 0; i < currLevelSize; i++){
TreeNode curr = q.poll();
level.add(curr.val);
if(curr.left != null){
q.offer(curr.left);
}
if(curr.right != null){
q.offer(curr.right);
}
}
result.add(level);
}
return result;
}
}
最短路径问题及变种
一、腐烂的橘子(多源变种):leetcode—994
- 题目描述:
提示:
1 <= grid.length <= 10
1 <= grid[0].length <= 10
grid[i][j] 仅为 0、1 或 2
-
分析
此题其实是最短路径的变种,我们已知的条件是:
1)路径起点:所有已经腐烂的橘子
2)整张图为无权图
我们要求的是:
1)最短路径:让所有橘子都腐烂的时间,即到达新鲜橘子的最短路径
难点:
1)多源:其实多源可以将所有源头也看做是某个空节点的所有子节点,他们处于同一层级,所以本质还是可以利用BFS解决。
所以满足BFS解决最短路径问题的条件,可以使用BFS解决此题。 -
代码
class Solution {
int[] dx = {1, -1, 0, 0};
int[] dy = {0, 0, -1, 1};
public int orangesRotting(int[][] grid) {
int result = 0;
//q用于存放待污染的橘子,横纵坐标
Queue<int[]> q = new LinkedList<int[]>();
int freshCount = 0;
//把所有已经腐烂的橘子放入queue中
for(int i = 0; i < grid.length; i++){
for(int j = 0; j < grid[0].length; j++){
if(grid[i][j] == 1){
freshCount++;
}
if(grid[i][j] == 2){
q.offer(new int[]{i, j});
}
}
}
//开始循环
while(freshCount > 0&&!q.isEmpty()){
result++;
int currLevelSize = q.size();
for(int i = 0; i < currLevelSize; i++){
int[] index = q.poll();
int x = index[0];
int y = index[1];
for(int j = 0; j < 4; j++){
int newX = x + dx[j];
int newY = y + dy[j];
if(newX >= 0 && newX < grid.length && newY >= 0 && newY < grid[0].length){
if(grid[newX][newY] == 1){
grid[newX][newY] = 2;
q.offer(new int[]{newX, newY});
freshCount--;
}
}
}
}
}
if(freshCount > 0){
result = -1;
}
return result;
}
}