重温数据结构与算法之深度优先搜索
文章目录
- 前言
- 一、实现
- 1.1 递归实现
- 1.2 栈实现
- 1.3 两者区别
- 二、LeetCode 实战
- 2.1 二叉树的前序遍历
- 2.2 岛屿数量
- 2.3 统计封闭岛屿的数目
- 2.4 从先序遍历还原二叉树
- 参考
前言
深度优先搜索(Depth First Search,DFS)是一种遍历或搜索树或图数据结构的算法。该算法从根节点开始(在图的情况下,选择一些任意的节点作为根节点),并在回溯之前尽可能地沿着每个分支进行探索。需要额外的内存,通常是一个堆栈,来跟踪到目前为止沿着指定分支发现的节点,这有助于回溯。
深度优先搜索算法的特点:
- 从一个起始节点开始,沿着一条路径不断访问邻接节点,直到没有未访问的邻接节点为止,然后回溯到上一个节点,继续访问其他邻接节点。
- 利用栈或递归来实现。
- 可以产生目标图的相应拓扑排序表。
深度优先搜索算法的优点:
- 简单易实现。
- 占用空间少。
- 可以找到从起始节点到任意可达节点的路径。
深度优先搜索算法的缺点:
- 不一定能找到最短路径或最优解。
- 可能会陷入死循环或无限递归。
深度优先搜索算法的应用场景:
- 拓扑排序 (课程安排、工程进度、依赖关系)
- 模拟游戏(如象棋、迷宫等)
- 连通性检测(如判断图中是否有环等)
- 旅行商问题(如求解最短路径等)
- 括号匹配(如检查表达式中的括号是否匹配等)
- 二叉树、线段树、红黑树、图等数据结构的遍历
在本文中,我们将介绍深度优先搜索算法的基本原理和实现方法,并通过一些例题来展示其应用。
一、实现
1.1 递归实现
从一个起始节点开始,沿着一条路径不断访问邻接节点,直到没有未访问的邻接节点为止,然后回溯到上一个节点,继续访问其他邻接节点,直到所有节点都被访问过为止。
示例代码如下:
public void dfs(int start) {visited[start] = true; //将起始节点标记为已访问for (int i = 0; i < n; i++) { //遍历邻接矩阵中start所在行if (matrix[start][i] == 1 && !visited[i]) { //如果存在边且未被访问过dfs(i); //递归调用dfs方法,以该节点为新起点进行遍历}}
}
1.2 栈实现
从一个起始节点开始,将其压入栈中,然后重复以下步骤:弹出栈顶元素,并将其标记为已访问;将该元素的所有未访问的邻接节点压入栈中。直到栈为空为止
示例代码如下:
public void dfs(int start) {Stack<Integer> stack = new Stack<>(); //创建栈对象stack.push(start); //起始节点入栈Set<Integer> visited = new HashSet<>(); //创建集合对象visited.add(start); //起始节点加入集合while (!stack.isEmpty()) { //只要栈不为空就继续循环int cur = stack.peek(); //获取栈顶元素但不出栈boolean flag = false; //设置标志位,表示是否有未访问过的邻接节点for (int i = 0; i < n; i++) { //遍历邻接矩阵中cur所在行if (matrix[cur][i] == 1 && !visited.contains(i)) { //如果存在边且未被访问过stack.push(i); //将该节点入栈visited.add(i); //将该节点加入集合System.out.print(i + " "); //打印该节点flag = true; //修改标志位为true,表示有未访问过的邻接节点break; //跳出循环,以该节点为新起点进行遍历}}if (!flag) { //如果没有未访问过的邻接节点,则说明已经到达最深处,需要回溯上一层继续遍历其他分支路径。stack.pop(); //将栈顶元素出栈 }}
}
下面是一个dfs搜索的动图
1.3 两者区别
- 递归实现是利用系统栈来保存当前节点的状态,当遇到死路时,自动回溯到上一个节点继续搜索。而栈实现是利用自定义的栈来保存当前节点的状态,当遇到死路时,手动弹出栈顶元素回溯到上一个节点继续搜索。
- 递归实现比较简洁易懂,但是效率不高,而且对于规模较大的图可能会导致栈溢出。而栈实现比较复杂一些,但是效率更高,而且可以避免栈溢出的问题。
- 递归实现和栈实现都需要一个标志数组来记录哪些节点已经被访问过,以防止重复访问或者陷入环路。
二、LeetCode 实战
2.1 二叉树的前序遍历
94. 二叉树的前序遍历
给你二叉树的根节点
root
,返回它节点值的 前序 遍历。
List<Integer> ans = new ArrayList(); //定义一个整数列表,用来存储前序遍历的结果
public List<Integer> preorderTraversal(TreeNode root) {if (root != null) { //如果当前节点不为空,才进行以下操作ans.add(root.val); //把当前节点的值加入列表preorderTraversal(root.left); //递归地对左子树进行前序遍历preorderTraversal(root.right); //递归地对右子树进行前序遍历}return ans; //返回前序遍历的结果
}
2.2 岛屿数量
200. 岛屿数量
给你一个由
'1'
(陆地)和'0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
// 定义一个二维数组pos,表示四个方向
int[][] pos = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };
// 定义一个变量ans,表示岛屿的数量
int ans = 0;// 定义一个方法numIslands,接受一个二维字符数组grid作为参数,返回岛屿的数量
public int numIslands(char[][] grid) {// 获取grid的行数和列数int m = grid.length, n = grid[0].length;// 定义一个二维布尔数组visited,表示每个位置是否被访问过boolean[][] visited = new boolean[m][n];// 遍历grid中的每个位置for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {// 如果当前位置是'1'且没有被访问过,则从该位置开始深度优先搜索,并将ans加一if (grid[i][j] == '1' && !visited[i][j]) {dfs(grid, visited, i, j);ans++;}}}// 返回ans作为结果return ans;
}// 定义一个方法dfs,接受一个二维字符数组grid、一个二维布尔数组visited、两个整数i和j作为参数,无返回值
public void dfs(char[][] grid, boolean[][] visited, int i, int j) {// 如果i或j越界或者当前位置是'0'或者已经被访问过,则直接返回if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == '0'|| visited[i][j]) {return;}// 将当前位置标记为已访问visited[i][j] = true;// 遍历四个方向,并递归调用dfs方法for (int[] p : pos) {dfs(grid, visited, i + p[0], j + p[1]);}}
2.3 统计封闭岛屿的数目
1254. 统计封闭岛屿的数目
二维矩阵
grid
由0
(土地)和1
(水)组成。岛是由最大的4个方向连通的0
组成的群,封闭岛是一个完全
由1包围(左、上、右、下)的岛。请返回 封闭岛屿 的数目。
// 定义一个二维数组pos来存储上下左右四个方向的偏移量
int[][] pos = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };
// 定义一个变量ans来记录封闭岛屿的个数
int ans = 0;public int closedIsland(int[][] grid) {// 判断矩阵是否为空,如果为空,直接返回0if (grid == null || grid.length == 0 || grid[0].length == 0) {return 0;}// 获取矩阵的行数和列数int m = grid.length, n = grid[0].length;// 遍历矩阵中的每一个元素for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {// 如果当前元素是岛屿(0),则调用dfs函数来检查它是否被水域(1)完全包围if (grid[i][j] == 0 && dfs(grid, i, j)) {// 如果dfs函数返回true,说明当前岛屿是封闭的,ans加一ans++;}}}// 返回ans作为最终答案return ans;
}
public boolean dfs(int [][] grid, int i, int j) {// 如果当前坐标超出了矩阵的边界,说明当前岛屿不是封闭的,返回falseif (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length) {return false;}// 如果当前元素是水域(1),说明没有遇到边界,返回trueif (grid[i][j] == 1) {return true;}// 将当前元素标记为水域(1),避免重复访问grid[i][j] = 1;// 使用一个for循环来遍历上下左右四个方向,并将结果进行逻辑与运算boolean res = true;for (int [] p: pos) {res &= dfs(grid, i + p[0], j + p[1]);}// 返回res作为dfs函数的结果return res;
}
2.4 从先序遍历还原二叉树
1028. 从先序遍历还原二叉树
我们从二叉树的根节点
root
开始进行深度优先搜索。在遍历中的每个节点处,我们输出
D
条短划线(其中D
是该节点的深度),然后输出该节点的值。(如果节点的深度为D
,则其直接子节点的深度为D + 1
。根节点的深度为0
)。如果节点只有一个子节点,那么保证该子节点为左子节点。
给出遍历输出
S
,还原树并返回其根节点root
。
int index = 0; // 定义全局变量indexpublic TreeNode recoverFromPreorder(String traversal) {int[] deep = Arrays.stream(traversal.split("[0-9]{1,10}")).mapToInt(String::length).toArray(); // 将输入字符串按照数字分割成数组deepint[] number = Arrays.stream(traversal.split("-{1,100}")).mapToInt(Integer::parseInt).toArray(); // 将输入字符串按照连字符分割成数组numberif (deep.length == 0) deep = new int[]{0}; // 如果deep为空,则赋值为[0]return dfs(deep, number); // 调用dfs函数并返回结果
}public TreeNode dfs(int [] deep, int [] number) {TreeNode treeNode = new TreeNode(number[index]); // 创建新的TreeNode对象并赋值int curHeight = deep[index]; // 获取当前节点的深度if (index + 1 < deep.length && curHeight == deep[index + 1] - 1) { // 判断是否有左子节点index++; // 将index加1treeNode.left = dfs(deep, number); // 递归调用dfs并赋值给左子节点}if (index + 1 < deep.length && curHeight == deep[index + 1] - 1) { // 判断是否有右子节点index++; // 将index加1treeNode.right = dfs(deep, number); // 递归调用dfs并赋值给右子节点}return treeNode; // 返回当前节点
}
参考
- https://en.wikipedia.org/wiki/Depth-first_search
- https://zh.wikipedia.org/wiki/深度优先搜索
- 深度优先搜索 —— 新手上路的一道坎
相关文章:
重温数据结构与算法之深度优先搜索
文章目录前言一、实现1.1 递归实现1.2 栈实现1.3 两者区别二、LeetCode 实战2.1 二叉树的前序遍历2.2 岛屿数量2.3 统计封闭岛屿的数目2.4 从先序遍历还原二叉树参考前言 深度优先搜索(Depth First Search,DFS)是一种遍历或搜索树或图数据结…...
STM32F103驱动LD3320语音识别模块
STM32F103驱动LD3320语音识别模块LD3320语音识别模块简介模块引脚定义STM32F103ZET6开发板与模块接线测试代码实验结果LD3320语音识别模块简介 基于 LD3320,可以在任何的电子产品中,甚至包括最简单的 51 作为主控芯片的系统中,轻松实现语音识…...
2023 最新可用Google镜像地址 长期更新
Google镜像说明 由于种种原因,国家还未开放Google搜索的使用。虽然可以通过某些技术手段实现访问,但是还是有一些同学需要借助Google搜索镜像才可以达到访问的目的;笔者特意搜集了一些2022年最新的Google搜索镜像供有需求的童鞋使用…...
MATLAB算法实战应用案例精讲-【优化算法】蝗虫优化算法(GOA)及其算法变种(附matlab和python代码实现)
目录 前言 算法原理 算法思想 GOA 算法的数学模型 迭代模型 算法流程...
数据结构与算法 顺序表、链表总结
文章目录顺序表1、顺序表的基本概念链表1 简介链表概念链表特点链表与数组的对比2 链表的类型分类链表循环单向链表1 简介概念2 数据存储和实现数据存储数据实现3 操作基本操作实现线性表(List):零个或多个数据元素的有限序列。在较复杂的线性…...
Nginx集群搭建-三台
1.使用root用户登录Linux服务器 2.创建用户 输入 adduser test 后回车 #test 为创建的用户 3.为创建的用户设置密码 输入 passwd test 后回车 输入两次密码 4.出现 passwd:所有的身份验证令牌已经成功更新。证明Linux新用户和密码创建成功 5.使用新用户test登录Linu…...
【算法】图的存储和遍历
作者:指针不指南吗 专栏:算法篇 🐾或许会很慢,但是不可以停下🐾 文章目录1. 图的存储1.1 邻接矩阵1.2 邻接表2. 图的遍历2.1 dfs 遍历2.2 bfs 遍历1. 图的存储 引入 一般来说,树和图有两种存储方式&#…...
文件如何批量复制保存在多个文件夹中
在日常工作中经常需要整理文件,比如像文件或文件夹重命名或文件批量归类,文件批量复制到指定某个或多个文件来中保存备份起来。一般都家最常用方便是手动一个一个去重命名或复制到粘贴到某个文件夹中保存,有没有简单好用的办法呢,…...
16N60-ASEMI高压MOS管16N60
编辑-Z 16N60在TO-220封装里的静态漏极源导通电阻(RDS(ON))为0.2Ω,是一款N沟道高压MOS管。16N60的最大脉冲正向电流ISM为48A,零栅极电压漏极电流(IDSS)为10uA,其工作时耐温度范围为-55~150摄氏度。16N60功耗…...
Open3D 多个点云配准(C++版本)
文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 多路配准(多个点云配准)是指在全局空间中对齐多个几何块的过程。输入的数据可以是点云或深度图像 P i P_i P...
java实现Hbase 增删改查
目录 一、新建一个maven工程 二、代码实现 2.1、配置hbase信息,连接hbase数据库 2.2、创建命名空间 2.3、创建表 2.4、删除表,删除之前要设置为禁用状态 2.5、添加数据 2.6、获取命令表空间 / tables列表 2.7、get方法查看表的内容 2.8、scan方法…...
1109. 航班预订统计 差分数组
1109. 航班预订统计 差分数组技巧适⽤于频繁对数组区间进⾏增减的场景 1.由数组a生成差分数组b{b[0]0,i0(或者b[0]a[0],i0)b[i]a[i]−a[i−1],i>01.由数组a生成差分数组b\left\{\begin{array}{l}b[0]0,i0(或者b[0]a[0],i0)\\ b[i]a[i]-a[i-1],i>0\end{array}\right. 1.由…...
图床搭建,使用typora上传
1. 准备gitee作为图床的仓库 新建仓库 准备仓库的私人令牌,后面配合使用 点击个人设置——》私人令牌 注意私人令牌,复制保存好,后面不能再看了 2. 准备PicGO,并进行相关配置 PicGo官方下载链接 下载安装好node.js,下载网址 安…...
低代码开发的优势是什么?
低代码开发的优势是什么?低代码开发这个概念这两年来经常出现在人们的视野中,市场对于低代码的需求也越来越庞大。 Gartner预测,到2025年,75%的大型企业将使用至少四种低代码/无代码开发工具,用于IT应用开发和公民开发计划。 可…...
Ip2Resion线上部署报数据越界及错误处理
上篇在本地测试调用Ip2Resigon解析行政区划 Ip2Region的Java本地实现运行正常,但部署到测试环境,抛出数组越界(java.lang.ArrayIndexOutOfBoundsException)异常。 环境信息 ip2Resion是2.7版本,对应文件后缀为 xdb。 …...
致敬我的C++启蒙老师,跟着他学计算机编程就对了 (文末赠书5本)
致敬我的C启蒙老师,跟着他学计算机编程就对了 摘要 讲述了一个故事,介绍了一位良师,一段因C而续写的回忆,希望对各位看官有所帮助和启发。 文章目录1 写在前面2 我的C启蒙老师3 谈谈老师给我的启发4 友情推荐5 文末福利1 写在前面…...
CSS中的伪元素和伪类
一直被伪类和伪元素所迷惑,以为是同一个属性名称,根据CSS动画,索性开始研究a:hover:after,a.hover:after的用法。 伪元素 是HTML中并不存在的元素,用于将特殊的效果添加到某些选择器。 对伪元素的描述 伪元素有两…...
逻辑优化基础-rewrite
简介 逻辑综合中的rewrite算法是一种常见的优化算法,其主要作用是通过对逻辑电路的布尔函数进行等效变换,从而达到优化电路面积、时序和功耗等目的。本文将对rewrite算法进行详细介绍,并附带Verilog代码示例。 一、算法原理 rewrite算法的…...
案例27-单表从9个更新语句调整为2个
目录 一:背景介绍 二:思路&方案 三:过程 1.项目结构 2.准备一个普通的maven项目,部署好mysql数据库 3.在项目中引入pom依赖 5.编写MyBitis配置文件 6.编写Mysql配置类 7.编写通用Update语句 8.项目启动类 四:总…...
Wordpress paid-memberships-pro plugins CVE-2023-23488未授权SQLi漏洞分析
目录 1.漏洞概述 2.漏洞等级 3.调试环境 4.漏洞代码 5 POC 1.漏洞概述 WordPress插件paid-memberships-pro版本<2.9.8中,容易受到REST路由“/pmpro/v1/order”的“code”参数中未验证的SQL注入漏洞的影响。攻击者可进行SQLi盲注,从而获取数据库权限。 CVE:...
【JavaWeb篇】JSTL相关知识点总结
目录 为什么会有JSTL? 什么是JSTL? 如何理解JSTL标准标签库呢? 如何使用JSTL? 第一步:引入JSTL标签库对应的jar包。 第二步:在JSP中引入要使用标签库。(使用taglib指令引入标签库。&#x…...
【蓝桥杯刷题】坑爹的负进制转换
【蓝桥杯刷题】——坑爹的负进制转换😎😎😎 目录 💡前言🌞: 💛坑爹的负进制转换题目💛 💪 解题思路的分享💪 😊题目源码的分享Ƕ…...
react+antdpro+ts实现企业级项目二:Strapi及认证登陆模块
在上一章节中,我们已经成功创建并登陆了系统,现在需要为系统添加权限和登录认证,以提高系统的安全性、数据保护、个性化服务和用户体验。此外,添加权限和登录认证还可以方便管理员进行用户和授权管理。为了快速开发前端࿰…...
Android ANR trace日志如何导出
什么是ANR ?上网搜索,一搜一大片,我就说个很容易识别的字眼,XXXAPP无响应 ANR trace日志如何导出?使用ADB命令: adb pull data/anr/trace.txt 你要存放的路径。查看ANR报错位置全局搜索你APP的包名&#x…...
Windows SSH 配置和SCP的使用
使用用户界面安装 ssh 功能 要在 Windows 10/11 上启用 SSH 服务器,请按照以下步骤操作: 按“Windows 键 I”打开“设置”菜单,然后选择“应用程序”。在左侧菜单栏中选择“应用和功能”。从列表中选择“可选功能”。 点击“添加功能”按钮…...
liunx 安装redsi和连接
liunx 安装redsi和连接 下载 (https://download.redis.io/releases/) 上传到 /usr/local目录 解压 tar -xvf redis-5.0.14.tar.gz 切换到 cd ./redis-5.0.14 编译 make 安装 make install 默认安装目录 /usr/local/bin/ 修改 ./redis-5.0.14/reds…...
接口里面可以写实现方法吗【可以】 、接口可以多继承吗【可以】
比如下面这道题: 问: 接口里面可以写方法吗? 答: 当然可以啊,默认就是抽象方法。 . 问: 那接口里面可以写实现方法吗? 答: 不可以,所有方法必须是抽象的。 . 问: 你确定吗࿱…...
【YOLOv8/YOLOv7/YOLOv5/YOLOv4/Faster-rcnn系列算法改进NO.57】引入可形变卷积
文章目录前言一、解决问题二、基本原理三、添加方法四、总结前言 作为当前先进的深度学习目标检测算法YOLOv8,已经集合了大量的trick,但是还是有提高和改进的空间,针对具体应用场景下的检测难点,可以不同的改进方法。此后的系列…...
统计学习--三种常见的相关系数
1)Pearson积差相关系数:用于量度两个变量X和Y之间的线性相关。它具有1和-1之间的值,其中1是总正线性相关性,0是非线性相关性,并且-1是总负线性相关性。Pearson相关系数的一个关键数学特性是它在两个变量的位置和尺度的…...
基于Django4.1.4的入门学习记录
基于Django4.1.4的入门学习记录Django创建Django项目创建工程工程目录说明运行开发服务器settings.py配置文件应用的创建创建应用模块应用模块文件说明App应用配置注册安装子应用数据模型ORM概述定义模型类生成数据库表查看数据库文件Admin管理工具管理界面本地化创建管理员注册…...
企业网站建设的一般要素/友情链接网站大全
文章目录进程创建fork函数初识fork函数返回值#fork为什么有两个返回值写时拷贝写时拷贝的触发如何理解子进程的创建fork常规用法fork调用失败的原因进程终止进程退出场景进程常见退出方法exit函数_exit函数exit和_exit的工作:return退出exit() vs return进程创建 f…...
网站网页设计师/如何去除痘痘有效果
5.各种数值NSArray和NSDictionary只能存储对象,而不能直接存储任何基本类型的数据,如int、float 或 struct。但是你可以用对象来封装基本数值。例如,将int型数据封装到一个对象中,然后就可以将这个对象放入NSArray或NSDictionary中…...
建设征婚网站/每日军事新闻
存储过程存储过程参数模式包括IN、OUT、 IN OUT。 IN(默认参数模式):表示当存储过程别调用时,实参值被传递给形参;形参起变量作用,只能读该参数,而不能修改该参数。IN模式参数可以是变量或表达式。OUT:表示…...
怎么做网站优化排名/网络推广平台
相邻数对 题目 给定n个不同的整数,问这些数中有多少对整数,它们的值正好相差1。 输入 输入的第一行包含一个整数n,表示给定整数的个数。 第二行包含所给定的n个整数。 输出 输出一个整数,表示值正好相差1的数对的个数。 输…...
营销型企业网站/seo外链平台
上一篇的介绍就足以使用struts2的校验功能;com.opensymphony.xwork2.validator.validators有个文件default.xml ,该文件中定义了Struts2框架内建的校验器,你可以查看struts2支持的校验类型,在校验文件中引用即可;下面介…...
网站域名怎么设置方法/免费seo公司
科目编号:3717 座位号 2018-2019学年度第二学期期末考试 天然气管道长输技术 试题 2018年 7 月 一、填空题(本大题共5空,每空5分,共计25分) 1.流体在管道中的流态划分为两大类:和。 2ÿ…...