1,概述
广度优先搜索(BFS),利用队列实现,特点是水泛涟漪;
深度优先搜索(DFS),使用递归实现(其实是用到了栈的形式,先进后出),特点是一条路走到黑;
2,代码模板实现
//DFS代码实现
void dfs(TreeNode root){
if(root==null){
return;
}
dfs(root.left);
dfs(root.right);
}
//BFS代码实现
void bfs(TreeNode root){
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
TreeNode node=queue.poll();
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right)
}
}
}
在二叉树上的表示如下:
DFS 与 BFS 对比(这个图借鉴了这位大佬的力扣博客https://pic.leetcode-cn.com/fdcd3bd27f4008948084f6ec86b58535e71f66862bd89a34bd6fe4cc42d68e89.gif)
典型例题
1.二叉树的深度
这个题很典型
//DFS解法
public int dfs(TreeNode root){
if(root==null){
return 0;
}
return Math.max(dfs(root.left),dfs(root.right))+1;
}
//BFS的解法
public int maxDepth(TreeNode root) {
Queue<TreeNode> queue=new LinkedList<>();
if(root==null) return 0;
int deepth=0;
queue.add(root);
while(!queue.isEmpty()){
int n=queue.size();
for(int i=0;i<n;i++){
TreeNode node=queue.poll();
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
deepth++;
}
return deepth;
}
2.层数最深叶子节点的和
这个题目用bfs简单一些,套用上面的模板
public int deepestLeavesSum(TreeNode root) {
if(root==null) return 0;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
int sum=0;
while(!queue.isEmpty()){
int n=queue.size();
sum=0;
for(int i=0;i<n;i++){
TreeNode node=queue.poll();
sum+=node.val;
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
}
return sum;
}
3.祖父节点值为偶数的节点和
这个问题还是利用bfs的层序遍历,仍然可以套用模板,代码如下:
//bfs的解法,先判断当前的节点值是否为偶数,如果为偶数,判断其孙子节点是否存在,如果存在,则累加
public int sumEvenGrandparent(TreeNode root) {
if(root==null) return 0;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
int sum=0;
while(!queue.isEmpty()){
TreeNode node=queue.poll();
if(node.val%2==0){
if(node.left!=null){
if(node.left.left!=null) sum+=node.left.left.val;
if(node.left.right!=null) sum+=node.left.right.val;
}
if(node.right!=null){
if(node.right.left!=null) sum+=node.right.left.val;
if(node.right.right!=null) sum+=node.right.right.val;
}
}
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
return sum;
}
当然这题也可以使用DFS的方法:先通过dfs找出所有满足题目要求的节点,然后再进行判断。具体同时维护三个节点(爷爷,父亲,当前节点),然后对爷爷进行判断,符合条件则把当前节点的值累加。然后继续维护(父亲,当前节点,当前节点的左右子节点)。这里进行了一个优化。其实我们在判断的时候没有用到爷爷节点和父节点,只是用到了他们的值,所以可以省去判断。在dfs的开始,我们用1代替祖父节点和父节点的值,进一步省去判断。
class Solution {
int sum=0;
public int sumEvenGrandparent(TreeNode root) {
dfs(1,1,root);
return sum;
}
private void dfs(int grandp_val,int p_val,TreeNode node){
if(node==null) return;
if(grandp_val%2==0) sum+=node.val;
dfs(p_val,node.val,node.left);
dfs(p_val,node.val,node.right);
}
}
BFS的应用
一、层序遍历问题
1,leetcode102.二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例: 二叉树:[3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
由上面的分析可知,此题使用BFS,由于要将每层的数保存到列表里,则需要判断一下每层一共有多少个数字,代码如下:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> lists=new ArrayList<>();
Queue<TreeNode> queue=new LinkedList<>();
if(root==null) return new ArrayList<>();
queue.add(root);
while(!queue.isEmpty()){
List<Integer> list=new ArrayList<>();
int n=queue.size();
for(int i=0;i<n;i++){
TreeNode node=queue.poll();
list.add(node.val);
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
lists.add(list);
}
return lists;
}
}
当然,这个题目如果非要用DFS也可以。因为DFS是一路向下遍历的,如果要实现每层节点值放在同一个列表里,就要引入一个level变量,在遍历的时候,将同一个level层的节点值放在同一个列表里,如果此时遍历的节点的level大于现在lists的size的话,就lists里新建一个list再存入。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> lists=new ArrayList<>();
if(root==null) return lists;
dfs(lists,root,0);
return lists;
}
private void dfs(List<List<Integer>> lists,TreeNode node,int level){
if(lists.size()<=level){ //此处要想清楚
lists.add(new ArrayList<>());
}
lists.get(level).add(node.val);
if(node.left!=null){
dfs(lists,node.left,level+1);
}
if(node.right!=null){
dfs(lists,node.right,level+1);
}
}
}
二、最短路径问题
这里需要解释一下为什么最短路径问题适合用BFS,而不是DFS,dfs的算法实现就是遍历所有可能存在的路径。因此要想得到最短路径,毕竟要遍历所有的路径进行比较。而使用bfs进行遍历的话,如果某个节点灭有左节点或者右节点,那么它就是最短路径的终点,此时就可以得出结果,没有继续遍历的必要了。下面看一个例题
1.地图分析
你现在手里有一份大小为 N x N 的「地图」(网格) grid,上面的每个「区域」(单元格)都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地,请你找出一个海洋区域,这个海洋区域到离它最近的陆地区域的距离是最大的。
我们这里说的距离是「曼哈顿距离」( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个区域之间的距离是 |x0 - x1| + |y0 - y1| 。
如果我们的地图上只有陆地或者海洋,请返回 -1。
示例 1:
输入:[[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释:
海洋区域 (1, 1) 和所有陆地区域之间的距离都达到最大,最大距离为 2。
示例2:
输入:[[1,0,0],[0,0,0],[0,0,0]]
输出:4
解释:
海洋区域 (2, 2) 和所有陆地区域之间的距离都达到最大,最大距离为 4。
解析:这个题目不是常见的二叉树,但是同样可以用层序遍历的方式,采用扩散的思想来解题(这个形容是看到了甜姨分享的题解才焕然大悟,之前做出来但是不知道怎么去形容)。具体做法是将所有陆地加入队列,然后同时向外扩散,没扩散一圈,数值加1,当地图上没有海洋的时候,此时的数值就是所求。具体代码如下:
public int maxDistance(int[] grid){
int[] dx={0,0,1,-1};
int[] dy={1,-1,0,0};
Queue<int[]> queue=new ArrayDeque<>();
int con=grid.length,row=grid[0].length;
//第一步把所有的陆地坐标都放入队列
for(int i=0;i<con;i++){
for(int j=0;j<row;j++){
if(grid[i][j]==1){
queue.offer(new int[]{i,j});
}
}
}
boolean hasOcean=false; //此处加它是因为全都是陆地没有海洋的情况
int point=null;
while(!queue.isEmpty()){
point=queue.poll();
int newx=point[0];
int nexy=point[1];
//这一步是把陆地的四周遍历一遍,直接再本来的数值上面加1
for(int i=0;i<4;i++){
int x=newx+dx[i];
int y=newy+dx[i];
//判断陆地四周坐标是否溢出和当前坐标是否是海洋
if(x>=0 && x<con && y>=0 && y<row && grid[x][y]==0){
hasOcean=true;
grid[x][y]=grid[newx][newy]+1;
queue.offer(new int[x][y]);
}
}
}
if(point==null || !hasOcean) return -1;
return grid[point[0]][point[y]]-1;
}
2.最小高度树
对于一个具有树特征的无向图,我们可选择任何一个节点作为根。图因此可以成为树,在所有可能的树中,具有最小高度的树被称为最小高度树。给出这样的一个图,写出一个函数找到所有的最小高度树并返回他们的根节点。
格式
该图包含 n 个节点,标记为 0 到 n - 1。给定数字 n 和一个无向边 edges 列表(每一个边都是一对标签)。
你可以假设没有重复的边会出现在 edges 中。由于所有的边都是无向边, [0, 1]和 [1, 0] 是相同的,因此不会同时出现在 edges 里。
输入: n = 4, edges = [[1, 0], [1, 2], [1, 3]]
0 | 1 / \ 2 3 输出: [1]
示例 2:
输入: n = 6, edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]] 0 1 2 \ | / 3 | 4 | 5 输出: [3, 4]
思路:其实就是把所有的情况放进一个二维数组,并且用一个数组记录每个数出现的个数,接下来的操作就像是剥洋葱一样,从最外面开始剥(也就是最外面一层的数出现的个数为1),具体实现如下:
class Solution {
private boolean[][] graph;
private boolean[] visited;
private int[] e;
private Queue<Integer> queue;
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
graph=new boolean[n][n];
visited=new boolean[n];
e=new int[n];
queue=new LinkedList<>();
//将所有的数压入二位数组,并且记录每个数出现的个数
for(int i=0;i<edges.length;i++){
graph[edges[i][0]][edges[i][1]]=true;
graph[edges[i][1]][edges[i][0]]=true;
e[edges[i][0]]++;
e[edges[i][1]]++;
}
while(n>2){ //此处判断大于2的原因是可能会存在一个或者两个节点都满足最小高度树,比如例二
findOuter(); //将最外层的数压入队列
while(!queue.isEmpty()){
Integer v=queue.poll();
e[v]--;
n--; //此处注意
visited[v]=true;
for(int i=0;i<graph[v].length;i++){
if(graph[v][i]){
e[i]--;
graph[v][i]=false;
graph[i][v]=false;
}
}
}
}
List<Integer> res=new ArrayList<>();
for(int i=0;i<visited.length;i++){
if(!visited[i])
res.add(i);
}
return res;
}
public void findOuter(){
for(int i=0;i<e.length;i++){
if(e[i]==1)
queue.add(i);
}
}
}
上面分享了一些dfs和bfs的典型例题,最后分享了几个bfs的应用。