Java-DFS(深度优先搜索)
原理
深度优先搜索的基本思路是从一个节点开始,依次访问它的每一个邻居节点,直到达到一个没有未被访问的邻居的节点为止。这个过程可以使用递归或者栈来实现。其特点是尽可能深入每一个分支,然后再回溯。
DFS算法常用于解决以下类型的问题:
- 路径搜索:在图中寻找一条特定路径。
- 图的连通性:判断图是否是连通的。
- 拓扑排序:在有向无环图(DAG)中,对节点进行排序。
- 强连通分量:找出一个有向图的强连通分量。
深度优先搜索的实现
1. 图的表示
在实际使用中,图的表示通常有以下几种方式:
- 邻接矩阵:适用于边较多的情况,但空间复杂度为 O(V^2),其中 V 是节点数。
- 邻接表:适用于边较少的图,空间复杂度为 O(V + E),其中 E 是边数。
在下面的示例中,我们使用邻接表来表示图,因为它存储效率更高,适合大多数情况。
2. 使用递归实现 DFS
以下是通过递归方式实现深度优先搜索的详细代码,并附带注释以帮助理解每个部分的功能
import java.util.*; public class DepthFirstSearch { private Map<Integer, List<Integer>> graph; // 图的邻接表 // 构造函数 public DepthFirstSearch() { graph = new HashMap<>(); // 初始化图 } // 添加边:从源节点到目标节点 public void addEdge(int source, int destination) { graph.putIfAbsent(source, new ArrayList<>()); // 如果源节点不存在,则添加 graph.get(source).add(destination); // 添加目标节点到源节点的邻接表中 } // 深度优先搜索的入口方法 public void dfs(int start) { Set<Integer> visited = new HashSet<>(); // 用于记录访问过的节点 dfsHelper(start, visited); // 调用递归助手方法 } // 递归助手方法 private void dfsHelper(int node, Set<Integer> visited) { if (visited.contains(node)) return; // 如果节点已访问,则返回 visited.add(node); // 标记节点为已访问 System.out.println(node); // 访问并打印当前节点 // 遍历所有邻居节点 for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) { dfsHelper(neighbor, visited); // 递归访问邻居节点 } } public static void main(String[] args) { DepthFirstSearch dfs = new DepthFirstSearch(); // 构建图:节点和边 dfs.addEdge(1, 2); dfs.addEdge(1, 3); dfs.addEdge(2, 4); dfs.addEdge(2, 5); dfs.addEdge(3, 6); dfs.addEdge(3, 7); System.out.println("深度优先搜索结果:"); dfs.dfs(1); // 从节点1开始搜索 }
}
3. 使用栈实现 DFS
栈的实现方式不直接依赖于系统的调用栈,这对于深度大、可能导致栈溢出的情况特别有用。以下是迭代实现的代码:
import java.util.*; public class DepthFirstSearchIterative { private Map<Integer, List<Integer>> graph; // 图的邻接表 // 构造函数 public DepthFirstSearchIterative() { graph = new HashMap<>(); // 初始化图 } // 添加边:从源节点到目标节点 public void addEdge(int source, int destination) { graph.putIfAbsent(source, new ArrayList<>()); // 如果源节点不存在,则添加 graph.get(source).add(destination); // 添加目标节点到源节点的邻接表中 } // 使用栈迭代实现深度优先搜索 public void dfsIterative(int start) { Set<Integer> visited = new HashSet<>(); // 记录访问过的节点 Stack<Integer> stack = new Stack<>(); // 创建栈 stack.push(start); // 将起始节点压入栈 while (!stack.isEmpty()) { // 只要栈不为空,继续遍历 int node = stack.pop(); // 弹出栈顶元素 if (!visited.contains(node)) { // 如果该节点未被访问 visited.add(node); // 标记为已访问 System.out.println(node); // 访问并打印当前节点 // 将所有邻居节点压入栈中 for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) { stack.push(neighbor); // 压入栈 } } } } public static void main(String[] args) { DepthFirstSearchIterative dfsIterative = new DepthFirstSearchIterative(); // 构建图:节点和边 dfsIterative.addEdge(1, 2); dfsIterative.addEdge(1, 3); dfsIterative.addEdge(2, 4); dfsIterative.addEdge(2, 5); dfsIterative.addEdge(3, 6); dfsIterative.addEdge(3, 7); System.out.println("深度优先搜索(迭代)结果:"); dfsIterative.dfsIterative(1); // 从节点1开始搜索 }
}
应用场景
- 路径查找:寻找从源节点到目标节点的路径,例如迷宫问题。
- 图的连通性:判断图中两个节点是否在同一个连通分量中。
- 拓扑排序:在有向无环图中进行节点排序,常用于任务调度。
- 强连通分量(Kosaraju算法或Tarjan算法):处理有向图中强连通分量的查找。
- 拼图解法:例如八数码问题,可以使用 DFS 搜索所有可能的状态直到找到目标状态。
让我们通过一个实际的例子来理解深度优先搜索(DFS)的应用场景。这次我们会用一个简单的迷宫问题来演示 DFS 的使用。
问题描述:迷宫寻路
想象一下,一个迷宫是一个二维网格,其中某些单元格是可通行的(代表放空格),而其他单元格是不可通行的(代表墙壁)。我们的任务是找到从起点到终点的路径。
迷宫示例
我们用下面的二维数组表示迷宫,其中 0 表示可通行,1 表示墙壁:
0 0 1 0 0
0 1 0 1 0
0 1 0 0 0
0 0 0 1 1
1 1 0 0 0
起点为 (0, 0),终点为 (4, 4)。
使用 DFS 寻找路径的代码
下面是一个 Java 实现的 DFS 代码,通过递归方式在迷宫中寻找从起点到终点的路径。
import java.util.*; public class MazeSolver { private static final int[][] DIRECTIONS = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; // 四个方向:下、右、上、左 private int[][] maze; private boolean[][] visited; private List<int[]> path; // 存储路径 public MazeSolver(int[][] maze) { this.maze = maze; this.visited = new boolean[maze.length][maze[0].length]; // 初始化访问标记 this.path = new ArrayList<>(); // 初始化路径 } public boolean solve(int x, int y, int endX, int endY) { // 检查边界条件:如果超出迷宫或当前位置是墙或已访问过则返回false if (x < 0 || y < 0 || x >= maze.length || y >= maze[0].length || maze[x][y] == 1 || visited[x][y]) { return false; } visited[x][y] = true; // 标记为已访问 path.add(new int[]{x, y}); // 添加位置到路径 // 如果到达终点,则返回true if (x == endX && y == endY) { return true; } // 递归尝试四个方向 for (int[] direction : DIRECTIONS) { int newX = x + direction[0]; int newY = y + direction[1]; if (solve(newX, newY, endX, endY)) { return true; // 找到路径 } } // 如果没有找到路径,则回溯 path.remove(path.size() - 1); // 移除最后一个位置 return false; // 返回false,表示未找到路径 } public List<int[]> getPath() { return path; // 获取找到的路径 } public static void main(String[] args) { int[][] maze = { {0, 0, 1, 0, 0}, {0, 1, 0, 1, 0}, {0, 1, 0, 0, 0}, {0, 0, 0, 1, 1}, {1, 1, 0, 0, 0} }; MazeSolver solver = new MazeSolver(maze); if (solver.solve(0, 0, 4, 4)) { System.out.println("找到路径:"); for (int[] position : solver.getPath()) { System.out.println(Arrays.toString(position)); } } else { System.out.println("没有找到路径。"); } }
}
-
迷宫表示:
- 迷宫使用二维数组
maze表示,0代表可以通行,1代表墙壁。
- 迷宫使用二维数组
-
定义方向:
DIRECTIONS数组定义了四个移动方向(下、右、上、左)。
-
DFS 寻找路径:
solve方法是实现 DFS 的核心:- 检查当前位置的有效性(边界检查、是否是墙、是否已访问)。
- 将当前位置标记为已访问,并添加到路径中。
- 如果找到终点,则返回成功。
- 通过递归尝试四个方向的移动。
- 如果未找到路径,则回溯,移除最后的步骤。
-
主函数:
- 初始化迷宫并调用
solve方法解决迷宫。 - 如果找到了一条路径,打印路径。
- 初始化迷宫并调用
注意事项
- 时间复杂度:DFS 的时间复杂度是 O(V + E),V 是节点数,E 是边数,因为每个节点和每条边都只被访问一次。
- 空间复杂度:若使用递归,最坏情况下(即图是链状结构)空间复杂度为 O(V)。如果使用显式栈,空间复杂度也是 O(V),最坏情况下(栈的深度达到 V)。
- 避免重复访问:使用集合来存储已访问的节点,确保每个节点只被访问一次。
- 处理图的变化:对于动态变化的图结构,可能需要重复调用 DFS 方法来更新状态
相关文章:
Java-DFS(深度优先搜索)
原理 深度优先搜索的基本思路是从一个节点开始,依次访问它的每一个邻居节点,直到达到一个没有未被访问的邻居的节点为止。这个过程可以使用递归或者栈来实现。其特点是尽可能深入每一个分支,然后再回溯。 DFS算法常用于解决以下类型的问题&…...
AI大模型编程能力对比:DeepseekClaudeGemini
在当今快速发展的技术领域,人工智能(AI)模型在编程和数据处理方面的应用越来越广泛。不同的AI模型因其独特的设计理念和技术优势,适用于不同的编程任务和场景。 本文将对三种主流的AI模型——DeepSeek v3、Gemini Flash 2.0 和 C…...
用C++实现点到三角形最小距离的计算
1、全部代码 #include <iostream> #include <cmath> #include <array> #include <algorithm>// 二维点结构体 struct Point2D {double x, y;Point2D(double x 0, double y 0) : x(x), y(y) {} };// 计算点到线段的最小距离 double pointToSegmen…...
解决前后端日期传输因时区差异导致日期少一天的问题
前端处理 1. 发送日期字符串而非时间戳 在前端使用日期选择器(如 el-date-picker)获取日期后,将日期转换为特定格式的字符串(如 YYYY-MM-DD)发送给后端,避免直接发送带有时区信息的时间戳或日期对象。这样…...
mmsegmentation自己的数据集+不同网络的config配对
比如说我们要用这个网络: 我们发现他内部继承了很多类,要想配对我们的数据集,就要进行父类的修改。 ../_base_/models/deeplabv3_unet_s5-d16.py, ../_base_/datasets/drive.py,../_base_/default_runtime.py, ../_base_/schedules/schedule…...
Golang官方编程指南
文章目录 1. Golang 官方编程指南2. Golang 标准库API文档 1. Golang 官方编程指南 Golang 官方网站:https://go.dev/ 点击下一步,查看官方手册怎么用 https://tour.go-zh.org/welcome/1 手册中的内容比较简单 go语言是以包的形式化管理函数的 搜索包名…...
ram的使用——初始化很重要
背景 ram是非常常用的ip,前人的经验告诉我们,如果不对ram进行初始化直接读写,不定态在实际上板时会出现不可预知的问题。 我们需要对ram进行初始化写0操作,代码如下。需要注意,复位释放时立马写入可能存在复位抖动的…...
doris:最佳实践
异步物化视图使用原则 时效性考虑: 异步物化视图通常用于对数据时效性要求不高的场景,一般是 T1 的数据。如果时效性要求高,应考虑使用同步物化视图。 加速效果与一致性考虑: 在查询加速场景,创建物化视图时&#x…...
[创业之路-299]:图解金融体系结构
一、金融体系结构 1.1 概述 金融体系结构是一个国家以行政的、法律的形式和运用经济规律确定的金融系统结构,以及构成这个系统的各种类型的银行和非银行金融机构的职能作用和相互关系。以下是对金融体系结构的详细分析: 1、金融体系的构成要素 现代金…...
RL--2
强化学习当中最难的两个点是: 1.reward delay; 2.agent的行为会影响到之后看到的东西,所以agent要学会探索世界; 关于强化学习的不同类型,可以分为以下三种: 一种是policy based:可以理解为它是…...
[JVM篇]分代垃圾回收
分代垃圾回收 分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时只有少量对象…...
Dify本地安装
目录 方式一docker安装: 方式二源码安装: Dify本地安装可以用docker方式,和源码编译方式。 先到云厂商平台申请一台Centos系统云主机,网络选择海外,需要公网IP,再按一下流程操作: 方式一doc…...
python | 两招解决第三方库安装难点
前言 python 被广泛应用的原因之一,便是拥有大量的第三方库,涵盖 web 开发、数据分析和机器学习等多个方面。 对于多数初学者来说,如何成功安装 python 第三方库成为了一大难点,总是因各种原因导致安装失败。 本文以自身经验&a…...
stm32mp15x 之 M4 使用 canfd
目录 序配置添加注坑参考 序 在使用 stm32mp15x 系列时,M4 有不少的坑,这里简单聊聊使用 canfd 时遇到的一些问题。 配置 这里使用 PLL4R 为 100M,用于 CANFD 的时钟 canfd 速率配置成 1M ,5M,其中数据传输速率为 5M…...
第七天:数据提取-正则表达式
每天上午9点左右更新一到两篇文章到专栏《Python爬虫训练营》中,对于爬虫有兴趣的伙伴可以订阅专栏一起学习,完全免费。 键盘为桨,代码作帆。这趟为期30天左右的Python爬虫特训即将启航,每日解锁新海域:从Requests库的…...
Python入门全攻略(六)
文件操作 文件路径 绝对路径:D:\pythonLearing\fileOperating.exe 相对路径:./fileOperating.exe # ./表示当前目录 # ../表示上一级目录 字符编码 字符集编码说明ASCll 最早的字符编码标准之一,基于拉丁字母的字符集,一共有128个字符GBK(国际码)用于简体中文的字符编码,…...
MongoDB副本集
副本集架构 对于mongodb来说,数据库高可用是通过副本集架构实现的,一个副本集由一个主节点和若干个从节点所组成。 客户端通过数据库主节点写入数据后,由从节点进行复制同步,这样所有从节点都会拥有这些业务数据的副本࿰…...
登录弹窗效果
1,要求 点击登录按钮,弹出登录窗口 提示1:登录窗口 display:none 隐藏状态; 提示2:登录按钮点击后,触发事件,修改 display:block 显示状态 提示3:登录窗口中点击关闭按钮࿰…...
C++上机_日期问题
1.求下一天的年月日 问题 已知某天的年月日,求下一天的年月日。 思路 参数:年,月,日(int) 返回值:void 处理:根据参数所给年月日,求下一天的年月日 思路: 1、定义一个数组&a…...
应对DeepSeek总是服务器繁忙的解决方法
最近由于访问量过大,DeepSeek服务器官网经常弹出:“服务器繁忙,请稍后再试”的提示,直接卡成PPT怎么办?服务器繁忙直接看到视觉疲劳: 解决DeepSeek卡顿问题 DeepSeek使用卡顿问题,是因为访问量…...
产销严重脱节,生产过剩与缺货问题反复出现怎么办?——2026年基于实在Agent的智慧供应链深度重构方案
站在2026年的时间节点回看,制造业的数字化转型已从简单的“信息化”跃迁至“智能体化”。 然而,即便在AI技术高度普及的今天,许多企业依然深陷于产销严重脱节的泥潭: 一边是仓库中堆积如山的过期库存,导致资金链极度紧…...
PCIe链路训练(LTSSM)实战避坑:从Detect到L0,你的仿真卡在哪一步了?
PCIe链路训练实战指南:从状态机原理到仿真问题定位 当你在深夜的实验室里盯着仿真波形,发现PCIe链路始终卡在Polling.Compliance状态时,那种挫败感我深有体会。三年前参与某款AI加速卡项目时,我们团队曾花了整整两周时间追踪一个诡…...
【雷达】基于Matlab GUI的中重频PD雷达仿真系统,根据输入参数仿真,图形界面简单
✅作者简介:热爱科研的Matlab仿真开发者,擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 🍎完整代码获取 定制创新 论文复现点击:Matlab科研工作室 🍊个人信条:格物致知,完整Matla…...
如何在 Taotoken 平台快速获取并管理你的 API Key
如何在 Taotoken 平台快速获取并管理你的 API Key 1. 注册与登录 Taotoken 平台 要开始使用 Taotoken 的服务,首先需要注册一个账号。访问 Taotoken 官方网站完成注册流程,使用邮箱验证后即可登录控制台。登录后你将看到仪表盘界面,这里提供…...
SWAT-CUP参数率定踩坑实录:从‘按钮灰色’到‘模拟太差’的9个实战解决方案
SWAT-CUP参数率定实战避坑指南:从安装配置到结果优化的全流程解决方案 水文模型参数率定是科研工作中既关键又令人头疼的环节。作为SWAT模型用户,我在过去三年里使用SWAT-CUP完成了七个流域的率定工作,期间踩过的坑比成功的案例还多。这篇文章…...
MAA明日方舟助手:终极自动化指南,告别重复劳动!
MAA明日方舟助手:终极自动化指南,告别重复劳动! 【免费下载链接】MaaAssistantArknights 《明日方舟》小助手,全日常一键长草!| A one-click tool for the daily tasks of Arknights, supporting all clients. 项目地…...
为AI工具协议MCP构建零信任安全代理:从OAuth到RBAC的实战指南
1. 项目概述:为AI工具协议筑起安全围墙最近在折腾AI Agent的开发,发现一个挺有意思但容易被忽视的安全问题。我们都在用Claude、Cursor、Copilot这些工具,它们背后连接各种数据源和服务,靠的是一个叫MCP(Model Context…...
嵌入式Day4
复合赋值运算符-*/%int main() {int a 20;a 10;printf("a is %d\n",a);a 20;a - 5;printf("a - is %d\n",a);a 20;a * 5 3 ;// 由于运算符 优先级 一定是计算 53 在赋值printf("a * is %d\n",a);a 20;a / 3 ;// printf("a /…...
AISMM成熟度模型落地失效?SITS2026用“能力-流程-角色-度量”四维校准法,3周止血、6周建模、12周固化!
更多请点击: https://intelliparadigm.com 第一章:SITS2026案例:AISMM驱动的组织变革 在SITS2026国际航天信息系统技术峰会中,欧洲航天局(ESA)与德国航空航天中心(DLR)联合实施的AI…...
终极指南:如何修复《恶霸鲁尼:奖学金版》在Windows 10/11上的崩溃问题
终极指南:如何修复《恶霸鲁尼:奖学金版》在Windows 10/11上的崩溃问题 【免费下载链接】SilentPatchBully SilentPatch for Bully: Scholarship Edition (fixes crashes on Windows 10) 项目地址: https://gitcode.com/gh_mirrors/si/SilentPatchBully…...
