当前位置: 首页 > news >正文

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开始搜索  }  
}

应用场景

  1. 路径查找:寻找从源节点到目标节点的路径,例如迷宫问题。
  2. 图的连通性:判断图中两个节点是否在同一个连通分量中。
  3. 拓扑排序:在有向无环图中进行节点排序,常用于任务调度。
  4. 强连通分量(Kosaraju算法或Tarjan算法):处理有向图中强连通分量的查找。
  5. 拼图解法:例如八数码问题,可以使用 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("没有找到路径。");  }  }  
}

 

  1. 迷宫表示

    • 迷宫使用二维数组 maze 表示,0 代表可以通行,1 代表墙壁。
  2. 定义方向

    • DIRECTIONS 数组定义了四个移动方向(下、右、上、左)。
  3. DFS 寻找路径

    • solve 方法是实现 DFS 的核心:
      • 检查当前位置的有效性(边界检查、是否是墙、是否已访问)。
      • 将当前位置标记为已访问,并添加到路径中。
      • 如果找到终点,则返回成功。
      • 通过递归尝试四个方向的移动。
      • 如果未找到路径,则回溯,移除最后的步骤。
  4. 主函数

    • 初始化迷宫并调用 solve 方法解决迷宫。
    • 如果找到了一条路径,打印路径。

 

注意事项

  • 时间复杂度:DFS 的时间复杂度是 O(V + E),V 是节点数,E 是边数,因为每个节点和每条边都只被访问一次。
  • 空间复杂度:若使用递归,最坏情况下(即图是链状结构)空间复杂度为 O(V)。如果使用显式栈,空间复杂度也是 O(V),最坏情况下(栈的深度达到 V)。
  • 避免重复访问:使用集合来存储已访问的节点,确保每个节点只被访问一次。
  • 处理图的变化:对于动态变化的图结构,可能需要重复调用 DFS 方法来更新状态

相关文章:

Java-DFS(深度优先搜索)

原理 深度优先搜索的基本思路是从一个节点开始&#xff0c;依次访问它的每一个邻居节点&#xff0c;直到达到一个没有未被访问的邻居的节点为止。这个过程可以使用递归或者栈来实现。其特点是尽可能深入每一个分支&#xff0c;然后再回溯。 DFS算法常用于解决以下类型的问题&…...

AI大模型编程能力对比:DeepseekClaudeGemini

在当今快速发展的技术领域&#xff0c;人工智能&#xff08;AI&#xff09;模型在编程和数据处理方面的应用越来越广泛。不同的AI模型因其独特的设计理念和技术优势&#xff0c;适用于不同的编程任务和场景。 本文将对三种主流的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. 发送日期字符串而非时间戳 在前端使用日期选择器&#xff08;如 el-date-picker&#xff09;获取日期后&#xff0c;将日期转换为特定格式的字符串&#xff08;如 YYYY-MM-DD&#xff09;发送给后端&#xff0c;避免直接发送带有时区信息的时间戳或日期对象。这样…...

mmsegmentation自己的数据集+不同网络的config配对

比如说我们要用这个网络&#xff1a; 我们发现他内部继承了很多类&#xff0c;要想配对我们的数据集&#xff0c;就要进行父类的修改。 ../_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 官方网站&#xff1a;https://go.dev/ 点击下一步&#xff0c;查看官方手册怎么用 https://tour.go-zh.org/welcome/1 手册中的内容比较简单 go语言是以包的形式化管理函数的 搜索包名…...

ram的使用——初始化很重要

背景 ram是非常常用的ip&#xff0c;前人的经验告诉我们&#xff0c;如果不对ram进行初始化直接读写&#xff0c;不定态在实际上板时会出现不可预知的问题。 我们需要对ram进行初始化写0操作&#xff0c;代码如下。需要注意&#xff0c;复位释放时立马写入可能存在复位抖动的…...

doris:最佳实践

异步物化视图使用原则​ 时效性考虑&#xff1a; 异步物化视图通常用于对数据时效性要求不高的场景&#xff0c;一般是 T1 的数据。如果时效性要求高&#xff0c;应考虑使用同步物化视图。 加速效果与一致性考虑&#xff1a; 在查询加速场景&#xff0c;创建物化视图时&#x…...

[创业之路-299]:图解金融体系结构

一、金融体系结构 1.1 概述 金融体系结构是一个国家以行政的、法律的形式和运用经济规律确定的金融系统结构&#xff0c;以及构成这个系统的各种类型的银行和非银行金融机构的职能作用和相互关系。以下是对金融体系结构的详细分析&#xff1a; 1、金融体系的构成要素 现代金…...

RL--2

强化学习当中最难的两个点是&#xff1a; 1.reward delay&#xff1b; 2.agent的行为会影响到之后看到的东西&#xff0c;所以agent要学会探索世界&#xff1b; 关于强化学习的不同类型&#xff0c;可以分为以下三种&#xff1a; 一种是policy based&#xff1a;可以理解为它是…...

[JVM篇]分代垃圾回收

分代垃圾回收 分代收集法是目前大部分 JVM 所采用的方法&#xff0c;其核心思想是根据对象存活的不同生命周期将内存划分为不同的域&#xff0c;一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时只有少量对象…...

Dify本地安装

目录 方式一docker安装&#xff1a; 方式二源码安装&#xff1a; Dify本地安装可以用docker方式&#xff0c;和源码编译方式。 先到云厂商平台申请一台Centos系统云主机&#xff0c;网络选择海外&#xff0c;需要公网IP&#xff0c;再按一下流程操作&#xff1a; 方式一doc…...

python | 两招解决第三方库安装难点

前言 python 被广泛应用的原因之一&#xff0c;便是拥有大量的第三方库&#xff0c;涵盖 web 开发、数据分析和机器学习等多个方面。 对于多数初学者来说&#xff0c;如何成功安装 python 第三方库成为了一大难点&#xff0c;总是因各种原因导致安装失败。 本文以自身经验&a…...

stm32mp15x 之 M4 使用 canfd

目录 序配置添加注坑参考 序 在使用 stm32mp15x 系列时&#xff0c;M4 有不少的坑&#xff0c;这里简单聊聊使用 canfd 时遇到的一些问题。 配置 这里使用 PLL4R 为 100M&#xff0c;用于 CANFD 的时钟 canfd 速率配置成 1M &#xff0c;5M&#xff0c;其中数据传输速率为 5M…...

第七天:数据提取-正则表达式

每天上午9点左右更新一到两篇文章到专栏《Python爬虫训练营》中&#xff0c;对于爬虫有兴趣的伙伴可以订阅专栏一起学习&#xff0c;完全免费。 键盘为桨&#xff0c;代码作帆。这趟为期30天左右的Python爬虫特训即将启航&#xff0c;每日解锁新海域&#xff1a;从Requests库的…...

Python入门全攻略(六)

文件操作 文件路径 绝对路径:D:\pythonLearing\fileOperating.exe 相对路径:./fileOperating.exe # ./表示当前目录 # ../表示上一级目录 字符编码 字符集编码说明ASCll 最早的字符编码标准之一,基于拉丁字母的字符集,一共有128个字符GBK(国际码)用于简体中文的字符编码,…...

MongoDB副本集

副本集架构 对于mongodb来说&#xff0c;数据库高可用是通过副本集架构实现的&#xff0c;一个副本集由一个主节点和若干个从节点所组成。 客户端通过数据库主节点写入数据后&#xff0c;由从节点进行复制同步&#xff0c;这样所有从节点都会拥有这些业务数据的副本&#xff0…...

登录弹窗效果

1&#xff0c;要求 点击登录按钮&#xff0c;弹出登录窗口 提示1&#xff1a;登录窗口 display:none 隐藏状态&#xff1b; 提示2&#xff1a;登录按钮点击后&#xff0c;触发事件&#xff0c;修改 display:block 显示状态 提示3&#xff1a;登录窗口中点击关闭按钮&#xff0…...

C++上机_日期问题

1.求下一天的年月日 问题 已知某天的年月日&#xff0c;求下一天的年月日。 思路 参数&#xff1a;年&#xff0c;月&#xff0c;日&#xff08;int) 返回值&#xff1a;void 处理&#xff1a;根据参数所给年月日&#xff0c;求下一天的年月日 思路: 1、定义一个数组&a…...

应对DeepSeek总是服务器繁忙的解决方法

最近由于访问量过大&#xff0c;DeepSeek服务器官网经常弹出&#xff1a;“服务器繁忙&#xff0c;请稍后再试”的提示&#xff0c;直接卡成PPT怎么办&#xff1f;服务器繁忙直接看到视觉疲劳&#xff1a; 解决DeepSeek卡顿问题 DeepSeek使用卡顿问题&#xff0c;是因为访问量…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

浅谈不同二分算法的查找情况

二分算法原理比较简单&#xff0c;但是实际的算法模板却有很多&#xff0c;这一切都源于二分查找问题中的复杂情况和二分算法的边界处理&#xff0c;以下是博主对一些二分算法查找的情况分析。 需要说明的是&#xff0c;以下二分算法都是基于有序序列为升序有序的情况&#xf…...

Mobile ALOHA全身模仿学习

一、题目 Mobile ALOHA&#xff1a;通过低成本全身远程操作学习双手移动操作 传统模仿学习&#xff08;Imitation Learning&#xff09;缺点&#xff1a;聚焦与桌面操作&#xff0c;缺乏通用任务所需的移动性和灵活性 本论文优点&#xff1a;&#xff08;1&#xff09;在ALOHA…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...