深搜(DFS)& 广搜(BFS)

2023-11-09

搜索的核心概念

问题求解树

是一种思维逻辑层面的结构,而非程序中的实际存储结构;思维结构可以表示无限的概念

设计搜索算法的核心关键点

设计问题求解树的状态

搜索剪枝和优化

在问题求解树搜索过程中,对于某些分支或子树通过某些条件的筛选不进行搜索。

搜索方法

对问题求解树不同的遍历方式

深度遍历 - Depth First Search

程序实现 =》 递归

广度遍历 - Breadth First Search

层序遍历 =》 队列
适合场景:求解最优化问题

练习

来自Leetcode
下面这个题目即可使用深搜又可使用广搜,理解下两种方法解题

993. 二叉树的堂兄弟节点

在二叉树中,根节点位于深度 0 处,每个深度为 k 的节点的子节点位于深度 k+1 处。

如果二叉树的两个节点深度相同,但 父节点不同 ,则它们是一对堂兄弟节点。

我们给出了具有唯一值的二叉树的根节点 root ,以及树中两个不同节点的值 x 和 y 。

只有与值 x 和 y 对应的节点是堂兄弟节点时,才返回 true 。否则,返回 false。

示例 1:
img

输入:root = [1,2,3,4], x = 4, y = 3
输出:false

示例 2:

img

输入:root = [1,2,3,null,4,null,5], x = 5, y = 4
输出:true

方法一: 深搜 dfs

思路:

  • 问题状态:被查找元素 & 当前节点 & 父节点 & 当前节点层深
  • 递归函数设计:
    • 入参: 当前节点 & 父节点
    • 返回值: 当前节点层深
    • 逻辑:结束条件 =》 当前节点为待查找元素 返回0, 当前节点为null, 返回-1(特殊值)
      状态扩展 =》 当前节点的左(右)子节点,父节点值重新置为当前节点值
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    private int dfs(TreeNode root, int val, TreeNode father) {
        if (root == null) return -1;
        if (root.val == val) return 0;
        father.val = root.val;
        int level;
        level = dfs(root.left, val, father);
        if (level != -1) return level + 1;
        father.val = root.val;
        level = dfs(root.right, val, father);
        if (level != -1) return level + 1;
        return -1;
    }

    public boolean isCousins(TreeNode root, int x, int y) {
        int l_x, l_y;
        TreeNode father_x = new TreeNode(-1), father_y = new TreeNode(-1);
        l_x = dfs(root, x, father_x);
        l_y = dfs(root, y, father_y);
        return l_x == l_y && father_x.val != father_y.val;
    }
}

方法二: 广搜 bfs

问题状态: 当前节点 & 父节点 & 当前节点层深

class Solution {
    class Node {
        TreeNode curr;
        TreeNode father;
        int level;

        public Node(TreeNode curr, TreeNode father, int level) {
            this.curr = curr;
            this.father = father;
            this.level = level;
        }
    }

    public boolean isCousins(TreeNode root, int x, int y) {
        int level_x = -1, level_y = -1;
        TreeNode father_x = new TreeNode(-1), father_y = new TreeNode(-1);
        Queue<Node> queue = new LinkedList<>();
        queue.offer(new Node(root, null, 0));
        while (!queue.isEmpty()) {
            Node node = queue.poll();
            if (node.curr.val == x) {//终止条件:判断是否为带查找元素
                level_x = node.level;
                father_x = node.father;
            }
            if (node.curr.val == y) {//终止条件:判断是否为带查找元素
                level_y = node.level;
                father_y = node.father;
            }
            if (node.curr.left != null) {//扩展状态【搜索树左右子树】
                queue.offer(new Node(node.curr.left, node.curr, node.level + 1));
            }
            if (node.curr.right != null) {//扩展状态【搜索树左右子树】
                queue.offer(new Node(node.curr.right, node.curr, node.level + 1));
            }
        }
        return level_x == level_y && father_x.val != father_y.val;
    }
}

1091. 二进制矩阵中的最短路径

给你一个 n x n 的二进制矩阵 grid 中,返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径,返回 -1 。

二进制矩阵中的 畅通路径 是一条从 左上角 单元格(即,(0, 0))到 右下角 单元格(即,(n - 1, n - 1))的路径,该路径同时满足下述要求:

路径途经的所有单元格都的值都是 0 。
路径中所有相邻的单元格应当在 8 个方向之一 上连通(即,相邻两单元之间彼此不同且共享一条边或者一个角)。
畅通路径的长度 是该路径途经的单元格总数。

示例 1:

img

输入:grid = [[0,1],[1,0]]
输出:2
示例 2:

img

输入:grid = [[0,0,0],[1,1,0],[1,1,0]]
输出:4

感觉这个题目就是广搜的比较又代表性的题目,其他的题目都是大同小异,可以深入理解下
问题状态:当前元素【一维坐标 & 二维坐标 & 距离起点距离】

class Solution {
    int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}, {1, -1}, {-1, 1}, {1, 1}, {-1, -1}};

    public int shortestPathBinaryMatrix(int[][] grid) {
        if (grid[0][0] != 0) return -1;//当矩阵左上角的第一个元素不为0, 则说明无法开始站在起始位置
        int n = grid.length;
        int m = grid[0].length;
        int[][] visit = new int[n][m];//存储原矩阵相应位置是否被访问过,用于剪枝
        visit[0][0] = 1;
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{0, 0, 1});
        while (!queue.isEmpty()) {
            int[] curr = queue.poll();
            if (curr[0] == n - 1 && curr[1] == n - 1) {//是否满足搜索终止条件 (走到终点)
                return curr[2];
            }
            for (int i = 0; i < dir.length; i++) {//扩展状态【二维矩阵方向数组dir】
                int x = curr[0] + dir[i][0];
                int y = curr[1] + dir[i][1];
                if (x < 0 || x >= n) continue;//剪枝 (下标越界)
                if (y < 0 || y >= m) continue;//剪枝 (下标越界)
                if (grid[x][y] != 0) continue;//剪枝 (题目要求)
                if (visit[x][y] != 0) continue;//剪枝 (重复访问)
                visit[x][y] = curr[2] + 1;
                queue.offer(new int[]{x, y, visit[x][y]});
            }
        }
        return -1;
    }
}

130. 被围绕的区域(dfs地图搜索)

给你一个 m x n 的矩阵 board ,由若干字符 'X''O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O''X' 填充。

示例 1:

img

分析:
四个边上的O没有被X围绕 =》由这些O向内扩展的所有可达的O均没有被X围绕; 由这些O向内扩展的所有不可达的O均被X围绕
问题状态:当前元素【一维坐标 & 二维坐标 & 是否为O且从四个边的O出发可达】
递归函数设计:

  • 入参:矩阵横坐标 & 矩阵纵坐标 & 原数组
  • 返回值: void
    • 逻辑:进入该递归函数的前提:矩阵中横纵坐标位置处元素为"O"且被访问到,将该元素update为小写"o";
    • 状态扩展 =》 在该递归函数内部进行状态扩展【矩阵方向数组】:剪枝 + 递归调用
class Solution {
    private int n;
    private int m;

    int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

    private void dfs(int i, int j, char[][] board) {
        board[i][j] = 'o';
        for (int k = 0; k < dir.length; k++) {
            int x = i + dir[k][0];
            int y = j + dir[k][1];
            if (x < 0 || x >= n) continue;
            if (y < 0 || y >= m) continue;
            if (board[x][y] != 'O') continue;
            dfs(x, y, board);
        }
    }

    public void solve(char[][] board) {
        n = board.length;
        m = board[0].length;
        for (int i = 0; i < n; i++) {
            if (board[i][0] == 'O') dfs(i, 0, board);
            if (board[i][m - 1] == 'O') dfs(i, m - 1, board);
        }
        for (int j = 0; j < m; j++) {
            if (board[0][j] == 'O') dfs(0, j, board);
            if (board[n - 1][j] == 'O') dfs(n - 1, j, board);
        }
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (board[i][j] == 'O') {
                    board[i][j] = 'X';
                } else if (board[i][j] == 'o') {
                    board[i][j] = 'O';
                }
            }
        }
    }
}

[473. 火柴拼正方形] (dfs)

还记得童话《卖火柴的小女孩》吗?现在,你知道小女孩有多少根火柴,请找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以把火柴连接起来,并且每根火柴都要用到。

输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。

分析:题目可以转化为:所拥有的火柴是否可以平均分为四组?

​ 初始化一个大小为4的一维数组(4个桶),每个元素初始化为火柴总长度/4(桶的容量);

​ 桶的容量为火柴总长度/4, 当将所有火柴都放入桶中时,代表4个桶均放满
问题状态: 第ind根火柴 & 桶的剩余容量 int[] arr
递归函数:

  • 入参:ind & int[]arr & 原数组int[] matchsticks
  • 返回值:题目所求“是否能用所有的火柴拼成正方形” boolean
  • 逻辑:
    • 满足结束条件, 当遍历到了所有火柴,说明可以将所有火柴都放入桶中,因为桶的总容量为火柴长度之和。即:火柴可以平均分为4组
    • 状态扩展: 剪枝 【当前桶的剩余容量放不下当前火柴 或者 当前桶的容量放不下当前火柴与剩余最短的火柴】
      递归调用【更新桶剩余容量】
class Solution {
    private boolean dfs(int ind, int[] arr, int[] matchsticks) {
        if (ind == -1) return true;//当ind 超限时,说明所有的火柴都放到桶中,且均放满
        for (int i = 0; i < arr.length; i++) {//遍历4个桶
            if (arr[i] < matchsticks[ind]) continue;//桶的容量不够火柴长度
            if (arr[i] == matchsticks[ind] || arr[i] >= matchsticks[ind] + matchsticks[0]) {
                arr[i] -= matchsticks[ind];//更新桶的剩余容量
                if (dfs(ind - 1, arr, matchsticks)) return true;
                arr[i] += matchsticks[ind];//若当前火柴放入当前桶后没有找到解,则需要将火柴从当前桶中拿出
            }
        }
        return false;
    }

    public boolean makesquare(int[] matchsticks) {
        Arrays.sort(matchsticks);
        int sum = 0;
        for (int matchstick : matchsticks) {
            sum += matchstick;
        }
        if (sum % 4 != 0) return false;
        int[] arr = new int[4];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = sum / 4;
        }
        return dfs(matchsticks.length - 1, arr, matchsticks);
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深搜(DFS)& 广搜(BFS) 的相关文章

  • PFC电路

    一 前言 PFC分无源和有源 无源PFC 也称被动式PFC 有源PFC 也称主动式PFC 无源PFC一般采用电感补偿方法使交流输入的基波电流与电压之间相位差减小来提高功率因数 但无源PFC的功率因数不是很高 只能达到0 7 0 8 有源PF

随机推荐

  • 蓝桥杯真题——组素数——python解析

    素数就是不能再进行等分的数 比如 2 3 5 7 11 等 9 3 3 说明它可以3等分 因而不是素数 我们国家在1949年建国 如果只给你 1 9 4 9 这4个数字卡片 可以随意摆放它们的先后顺序 但卡片不能倒着摆放啊 我们不是在脑筋急
  • 使用JSP完成cookie记住用户名和密码

    2019 10 11 JSP笔记 使用JSP完成cookie记住用户名密码
  • 布局资源(layout)的简单使用

    随时随地阅读更多技术实战干货 获取项目源码 学习资料 请关注源代码社区公众号 ydmsq666 布局资源是Android中最常用的一种资源 Android可以将屏幕中组件的布局方式定义在一个XML中 这有点像Web开发中的HTML页面 我们
  • 深入理解Hadoop YARN中的Container概念

    在学习Hadoop YARN Hadoop 2 0新引入的通用资源管理系统过程中 总会遇到Container这一概念 由于中文资料的缺乏 很多人对Container这一概念仍非常的模糊 它与Linux Container是什么关系 它是否能
  • 第一次开博,转一下《没有风投的创业法则》

    有人曾经对我说 一个创业者得到风险投资的几率如同在一个晴天下站在游泳池里被闪电击中一样 但是在我看来 这种比喻还是过于乐观了 在现在这个热钱涌动的商业 社会 好的 企业 从来不缺投资 当然前提必须是这是一家优秀的企业 至少也得是看起来有前途
  • shell 字符串处理汇总

    http blog chinaunix net uid 124706 id 3475936 html 字符串 简称 串 有限字符的序列 数据元素为字符的线性表 是一种数据的逻辑结构 在计算机中可有不同的存储结构 在串上可进行求子串 插入字符
  • vue js vue实现前端模糊匹配搜索,借助js自带的filter方法和正则表达式筛选所需元素

    vue js vue实现前端模糊匹配搜索 借助js自带的filter方法和正则表达式筛选所需元素 参考 vue实现的前端模糊匹配搜索 js 查找特定字符 模糊查询 不区分大小写
  • 二级菜单打开一个时其他关闭_blender2.8教程 顶部菜单栏

    顶栏 菜单 程序菜单 启动画面 打开 启动画面 支持Blender 开发筹资 打开开发基金网站 Blender商店 打开Blender商店 关于 发布说明 打开最新发布说明 Blender官网 打开Blender官网 贡献者名单 打开贡献者
  • 微信小程序开发 Request Headers: Provisional headers are shown

    在微信小程序开发的时候 写了两个API请求 请求A 请求A wx request url https wx baidu com api wx getBallByDate method POST dataType json data date
  • shrio验证cookie有效性

    shrio验证cookie有效性 概述 shrio中提供cookie管理的功能 当用户选择了rememberMe 则下次不需要再登录 而是直接通过本地记录的cookie进行验证 然后就可以访问权限为user的页面 问题 shiro提供清除用
  • 数据表的基础操作(五)数据的修改

    文章目录 修改数据 UPDATE 一 修改有数据 实例1 二 修改指定数据 实例2 修改数据 UPDATE 随着时间的推移和数据的更新 所以我们要对表存储的数据进行修改 一 修改有数据 语法 UPDATE 表名 SET 字段1 数据1 字段
  • Super超级ERP系统---(8)订单管理--订单创建

    订单管理是ERP系统中一个重要模块 客户下订单 ERP通过订单来为客户进行配送 订单模块主要包括订单创建 订单修改 订单审核 订单取消 订单分配 订单打印 订单拣货 订单出库 在随后的几节里我们看看这些每个模块是怎么设计运行的 1 订单创建
  • 微软语音合成 报错 Handler dispatch failed; nested exception is java.lang.UnsatisfiedLinkError: com.microsoft

    查找文档 发现操作系统问题 centos某个版本有问题 换成其他操作系统就好了
  • STM32 的ADC解析

    在嵌入式系统中 被测控的对象 如温度 压力 流量 速度 电压等 都是连续变化的物理量 这种连续变化的物理量通常被称为模拟量 当计算机参与测控时 计算机处理的信号是数字量 数字量指的是时间和数字上都离散的量 能将模拟量转换为数字量的器件称为模
  • java png 缩放_JAVA 图片的缩放,和压缩,PNG背景透明

    public class ImgFactory private Logger log LoggerFactory getLogger ImgFactory class private File saveFile private Buffer
  • 无法从静态上下文中引用非静态变量

    2019独角兽企业重金招聘Python工程师标准 gt gt gt Question 无法从静态上下文中引用非静态变量 解决 因为我们知道静态的方法可以在没有创建实例时使用 而申明为非静态的成员变量是一个对象属性 它只有在对象存在时引用 因
  • Ubuntu上安装 Emacs 24的几种方法

    1 首选当然是在Ubuntu Software Center 中找或者 apt get install emacs 可惜的是只有emacs23版本的 所以此路不通 放弃 现在Ubuntu12 04开始已经有了Emacs24 不过还是不推荐
  • js中使用中文作为变量名

    我觉得这样写js或许更科学些 免写注释了
  • Kubernetes进阶学习之集群维护与升级实践

    0x00 Kubernetes Etcd 数据备份与恢复 1 备份 ETCD 数据实践 2 恢复 ETCD 数据实践 0x01 Kubernetes 单 Master 节点 次版本 升级实践 0x02 Kubernetes 单 Master
  • 深搜(DFS)& 广搜(BFS)

    搜索的核心概念 问题求解树 是一种思维逻辑层面的结构 而非程序中的实际存储结构 思维结构可以表示无限的概念 设计搜索算法的核心关键点 设计问题求解树的状态 搜索剪枝和优化 在问题求解树搜索过程中 对于某些分支或子树通过某些条件的筛选不进行搜