六、BFS广度优先搜索

六、BFS广度优先搜索

总结
  • 遍历
  • 最短路径
  • 双向BFS

最重要的是层的概念

广度优先搜索有个基础模板:

1、root为空就直接返回;

2、queue队列q,先放入root;

3、while的循环条件是队列q不为空—取当前队列的大小(也就是这一层元素的个数)—for循环这一层-取出顶层元素(对此元素的左右进行添加);

遍历意味着:我在此时能过拿到这个元素,并对这个元素进行操作。

总:

  • while(遍历所有)
  • for(遍历一层)
  • q.poll()拿出一个
  • 加值-----很关键:加的是他下一次所有可能情况----这个层的所有元素的可能情况就构成了下一层
public void levelOrder(TreeNode root) {
	// 1、为空返回
    if(root == null) return;
    // 2、Queue队列
    Queue<TreeNode> q = new LinkedList<>();
    q.offer(root);

	// 3、①进行while循环
    while(!q.isEmpty()){
        // ②这一层的元素个数
        int size = q.size();
        // 对这一层元素进行遍历
        for(int i = 0; i < size; i++){
            // ③取值
            TreeNode cur = q.poll();

            // 对值得操作
            sout(cur.val);

            // ④加可能情况
            q.offer(cur下一次的可能情况);
        }
        res.add(level);
    }

    return res;
}

广度搜索有个明确得目标:路径最短(上述基础上)-----把这个值拿出来符合某个情况

  • 基础条件:遍历每一层元素的时候,到这一层之前所走的路步数都是一样的;

  • 结束条件:当某一层的每个元素符合了结束条件,由于该层的元素往上路的步数都一直,到它就变了,所以它是最短,即return

public int minDepth(TreeNode root) {
    if(root == null) return 0;
    
    Queue<TreeNode> q = new LinkedList<>();
    q.offer(root);

    int step = 0;
    while(!q.isEmpty()){
        int size = q.size();
		// ①到此层所走的步数
        step++;
        for(int i = 0; i < size; i++){
            TreeNode cur = q.poll();
			
            // ②结束条件
            if(满足结束条件) return step;
			
            // ③所有可能情况
            q.offer(cur下一次的可能情况);
        }
    }
    return step;
}

双向BFS

1、条件是最短路径的目标明确

2、双向BFS的是能实现的依据:

由于随着每一层的元素越来越多,那么下一层涵盖的元素会因为上一次元素的增多而增多,因为下一层来源于上一层下一次可能的所有情况么;

因此,由于目标值明确,以目标值出发,就像以根节点出发一样:起始都是1个点;结束条件就在两者交汇处,即最短距离。

3、关键点在于:

①起始值与目标值二者队列的存储—用俩个队列进行存储

②下一层的存储:Queue temp

③结束条件:当前元素包含在q2中

④双向的交换

public int ladderLength(String beginWord, String endWord) {
    // ①起始值与目标值二者队列的存储
    Queue<String> q1 = new LinkedList<String>();
    Queue<String> q2 = new LinkedList<String>();
    q1.offer(beginWord);
    q2.offer(endWord);

    int step = 0;
    while(!q1.isEmpty()){
		// ②q1或q2的暂时存储
        Queue<String> temp = new LinkedList<String>();
        
        int size = q1.size();
        step++;
        
        for(int i = 0; i < size; i++){
            TreeNode cur = q.poll();
            // ③结束条件
            if(cur包含在q2中) return step;
            
        	temp.offer(cur下一次的可能情况);
        }
    //④、交换
        q1 = q2;
        q2 = temp;
    }
    return 0;
}

1、遍历

1.1 子对象是叶结点
题目地址(102. 二叉树的层序遍历)

https://leetcode-cn.com/problems/binary-tree-level-order-traversal/

题目描述
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

示例 2:
输入:root = [1]
输出:[[1]]

示例 3:
输入:root = []
输出:[]

提示:
树中节点数目在范围 [0, 2000] 内
-1000 <= Node.val <= 1000
代码
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new LinkedList<>();
        // 1、为空直接返回
        if(root == null) return res;
        
        // 2、加入初始值
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);

        // 3、①所有值的遍历
        while(!q.isEmpty()){
            // ②当前层
            List<Integer> level = new LinkedList<>();
            int size = q.size();
            for(int i = 0; i < size; i++){
                // ④取出这一层得一个值
                TreeNode cur = q.poll();
                
                // 对这个值进行操作
                level.add(cur.val);

                // ⑤加值
                if(cur.left!=null) q.offer(cur.left);
                if(cur.right!=null) q.offer(cur.right);
            }
            res.add(level);
        }
        return res;
    }
}
1.2 子对象是图的邻居
题目地址(207. 课程表)

https://leetcode-cn.com/problems/course-schedule/

题目描述
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程  bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。

示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。

提示:

1 <= numCourses <= 105
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i] 中的所有课程对 互不相同
代码

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        // 下一次的可能性
        int[] indegree = new int[numCourses];
        List<Integer>[] graph = new LinkedList[numCourses];
        for(int i = 0;i < graph.length;i++){
            graph[i] = new LinkedList<Integer>();
        }
        for(int[] edge:prerequisites){
            int from = edge[1];int to = edge[0];
            indegree[to]++;
            graph[from].add(to);
        }

        // 1、添加初始值
        Queue<Integer> q = new LinkedList<>();
        for(int i = 0; i < indegree.length; i++){
            if(indegree[i] == 0)
            q.offer(i);
        }

        // 2、所有值的遍历,没有层的概念,记录元素的个数
        int count = 0;
        while(!q.isEmpty()){
            int cur = q.poll();
            count++;
            // 3、下个的可能性,与是否添加进去
            for(int neighbor : graph[cur]){
                indegree[neighbor]--;
                if(indegree[neighbor] == 0){
                    q.offer(neighbor);
                }
            }
        }
        // 3、对比是否与课程数相同
        return count == numCourses;
    }
}
1.3 子对象是邻接点
题目地址(542. 01 矩阵)

https://leetcode-cn.com/problems/01-matrix/

题目描述
给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。

示例 1:

输入:mat = [[0,0,0],[0,1,0],[0,0,0]]
输出:[[0,0,0],[0,1,0],[0,0,0]]

提示:

m == mat.length
n == mat[i].length
1 <= m, n <= 104
1 <= m * n <= 104
mat[i][j] is either 0 or 1.
mat 中至少有一个 0 
代码
class Solution {
    public int[][] updateMatrix(int[][] mat) {
        int m = mat.length; int n = mat[0].length;
        Queue<int[]> q = new LinkedList<>();
        HashSet<Integer> set = new HashSet<>();
        // 1、加入初始值,mat[i][j]=0,最短距离就是它本身,依次往后阔大层数
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(mat[i][j] == 0){
                    q.offer(new int[]{i,j});
                    set.add(i*n+j);
                }
            }
        }

        // 2、每一层的循
        int step = 0;        
        while(!q.isEmpty()){
            int size = q.size();
            for(int i = 0;i < size;i++){
                int[] edge = q.poll();
                int x = edge[0];int y = edge[1];
                
                // 3、把每一层的值给每一个数
                mat[x][y] = step;

                // 4、下一层可能的数的坐标
                int[][] neighbors = new int[][]{{1,0},{0,1},{-1,0},{0,-1}};
                for(int[] neighbor:neighbors){
                    int x1 = x + neighbor[0];
                    int y1 = y + neighbor[1];
                    if(x1 < 0||x1 >= mat.length||y1 < 0||y1 >= mat[0].length) continue;
                    if(set.contains(x1*n+y1)) continue;// 防止多加
                    q.offer(new int[] {x1,y1});
                    set.add(x1*n+y1);
                }
            }
            step++;
        }
        return mat;
    }
}

2、最短路径

2.1 最小高度-子对象叶结点
题目地址(111. 二叉树的最小深度)

https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/

题目描述
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
 
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:2

示例 2:
输入:root = [2,null,3,null,4,null,5,null,6]
输出:5
 
提示:
树中节点数的范围在 [0, 105] 内
-1000 <= Node.val <= 1000
代码
class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) return 0;

        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);

        int step = 0;
        while(!q.isEmpty()){
            int size = q.size();
            // ①到此层所走的步数
            step++;
            for(int i = 0; i < size; i++){
                TreeNode cur = q.poll();
                
                // ②结束条件
                if(cur.left == null&&cur.right == null) return step;
                
                // ③所有可能情况
                if(cur.left!=null) q.offer(cur.left);
                if(cur.right!=null) q.offer(cur.right);
            }
        }
        return step;
    }
}

3、双向BFS

3.1 额外条件是不重复且在规定字典中
题目地址(127. 单词接龙)

https://leetcode-cn.com/problems/word-ladder/

题目描述
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk:
每一对相邻的单词只差一个字母。
 对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
sk == endWord
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。

示例 1:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。

示例 2:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:0
解释:endWord "cog" 不在字典中,所以无法进行转换。

提示:

1 <= beginWord.length <= 10
endWord.length == beginWord.length
1 <= wordList.length <= 5000
wordList[i].length == beginWord.length
beginWord、endWord 和 wordList[i] 由小写英文字母组成
beginWord != endWord
wordList 中的所有字符串 互不相同
代码

class Solution {
    HashSet<String> map;
    HashSet<String> visit;
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        // 为取下一次肯能行且不重复做准备
        map = new HashSet<>();
        visit = new HashSet<>();
        for(String st:wordList){
            map.add(st);
        }

        if(!map.contains(endWord)) return 0;

        // ①起始值与目标值的储存:
        // 用set是因为,方便取值,此时每一次的取值存放在temp中,
        // 不会引发新增值而导致q原本的变化
        HashSet<String> q1 = new HashSet<String>();
        HashSet<String> q2 = new HashSet<String>();
        q1.add(beginWord);
        q2.add(endWord);

        int step = 0;
        while(!q1.isEmpty()&&!q2.isEmpty()){
            // ②q1或q2的存储
            HashSet temp = new HashSet<>();     
            step++;

            for(String st:q1){
                // 本题其他条件,不允许重返
                if(visit.contains(st)) continue;
                visit.add(st);
                // ③当q2中包括这个值,说明找到了
                // 特别说明的是:当以目标向上遍历时,
                // q2存的是上一次向下遍历时下一层的可能情况
                if(q2.contains(st)) return step;

                // 下一层可能情况
                List<String> neighbors = findNeighbors(st);
                for(String st1:neighbors){
                    if(!visit.contains(st1)){
                        temp.add(st1);
                    }
                }
            }
            // ④双向交换,q1已经用完了
            q1 = q2;
            q2 = temp;
        }
        return 0;
    }

    private  List<String> findNeighbors(String cur){
         List<String> res = new LinkedList<>();

         for(int i = 0;i < cur.length();i++){
             for(char ch = 'a';ch<='z';ch++){
                 char[] chs = cur.toCharArray();
                 chs[i] = ch;
                 String temp = new String(chs);
                 if(map.contains(temp)&&!visit.contains(temp)) res.add(temp);
             }
         }
         return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值