1. BFS的适用场景
1)分层遍历
- 一层一层地遍历一个图、树或矩阵
- 简单图最短路径(简单图的定义是,图中的所有边长都一样)
2)连通块问题
- 通过图中一个点找到其他所有连通的点
- 找到所有方案问题的一种非递归实现方式
3)拓扑排序
- 实现容易度远超DFS
2. BFS实现方式 (分层遍历)
1) 单队列方式
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if(root == null){
return result;
}
Queue<TreeNode> queue = new LinkedList<>();
//step1: 把第一层节点放入队列中
queue.offer(root);
//step2: 当队列非空时,遍历该层节点
while(!queue.isEmpty()){
int size = queue.size();
List<Integer> level = new ArrayList<>();
//step3: 由上一层节点,拓展出下一层节点
for(int i=0;i<size;i++){
TreeNode node = queue.poll();
level.add(node.val);
if(node.left != null){
queue.offer(node.left);
}
if(node.right != null){
queue.offer(node.right);
}
}
result.add(level);
}
return result;
}
2)双队列方式
思路:将一层的节点放入一个队列中,遍历该队列的所有节点,将所有子节点放入另一个队列,以此类推直至队列为空
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if(root == null){
return result;
}
//因为不需要用到poll功能来删除队首元素,用数组保存节点就可以
List<TreeNode> queue = new ArrayList<>();
queue.add(root);
while(!queue.isEmpty()){
//用一个新的数组来存放下一层的节点
List<TreeNode> next_queue = new ArrayList<>();
result.add(toIntegerList(queue));
for(TreeNode node:queue){
if(node.left != null){
next_queue.add(node.left);
}
if(node.right != null){
next_queue.add(node.right);
}
}
queue = next_queue;
}
return result;
}
private List<Integer> toIntegerList(List<TreeNode> queue){
List<Integer> level = new ArrayList<>();
for(TreeNode node:queue){
level.add(node.val);
}
return level;
}
3) DummyNode方式实现
思路:在每层节点间放一个虚拟节点(null),用来识别分层
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if(root == null){
return result;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
queue.offer(null);
List<Integer> level = new ArrayList<>();
while(!queue.isEmpty()){
TreeNode node = queue.poll();
if(node == null){
if(level.size() == 0){
break;
}
result.add(level);
queue.offer(null);
level = new ArrayList<Integer>();
continue;
}
level.add(node.val);
if(node.left != null){
queue.offer(node.left);
}
if(node.right != null){
queue.offer(node.right);
}
}
return result;
}
3. 图论
1)什么是图 (Graph) ?
图是顶点(vertex) 和边(edge) 的集合,分为有向图和无向图。另外,树也是一种特殊的图。
2)BFS树 vs BFS图
在二叉树中做BFS与在图中做BFS最大的区别就是,二叉树中无需使用HashSet来存储已访问过的节点。因为二叉树这种数据结构上下层关系分明,没有环。
3)存储图的数据结构
- 邻接矩阵 Adjacency Matrix:用一个二维数组 int[][] matrix,来表示每两个点之间是否连通
- 邻接表 Adjacency List:每个点上存储自己有哪些邻居。
可以使用HashMap和HashSet搭配的方式来存储邻接表
Map<Integer,Set> adList = new HashMap<Integer,HashSet<>>();
也可以使用自定义的类来实现邻接表
class DirectedGraphNode{
int label;
List<DirectedGraphNode> neighbors;
......
}
4. 总结
1)广度优先搜索
涉及到层序遍历、连通块、最短路径等问题,大多都用BFS。
首先需要一个队列queue存储每一层的节点,不断拓展出下一层。
其次还需要创建一个数组visited (若在涉及网格、坐标等问题中,需要创建二维数组) 或 HashSet集合visited,用来标明访问过的节点。
2)双向广度优先搜索
当明确知道目标顶点的情况下,可以分别从起点和终点执行广度优先遍历,直到遍历的部分有交集。
可以用两个集合 HashSet来分别存储从起点BFS的该层节点,和从终点BFS的该层节点。并且需要一个总的集合visited来存放已访问过的节点。
5. 相关例题
Leetcode: 102. 二叉树的层序遍历, 1293. 网格中的最短路径, 127. 单词接龙,
LintCode: 1179 · 朋友圈, 630 · 骑士的最短路径II
6. 拓展
1)三种遍历
- 先序遍历 - 根左右
- 中序遍历 - 左根右
- 后序遍历 - 左右根
2)break & continue
break用法:break用于switch语句中,终止switch语句;break用于循环时,跳出循环。
continue用法:continue用于循环中,跳过本次循环,继续执行下一次循环。
3)HashSet
- 简介:HashSet是基于HashMap实现的,是一个不允许有重复元素的集合。HashSet允许有null元素,是无序的。
- 无参构造:例 HashSet<String> set = new HashSet<String>();
- 参数为 (Collection) 另一个集合来构造:HashSet<String> set = new HashSet<>(wordList); wordList是一个数组
- add() :add方法调用了HashMap的put方法。例 set.add("peter");
- contains() :用来判断元素是否存在于集合当中,底层调用了HashMap的containsKey方法,返回值类型boolean。例 set.contains("peter")
- remove() :用来删除集合中的指定元素,底层调用了HashMap的remove方法,返回值类型boolean。例 set.remove("peter");
- clear方法:用来删除集合中所有元素。例 set.clear();
问:树图关系的解析