深度优先搜索(DFS)

深度优先搜索(DFS):Java 实现与原理解析

深度优先搜索(Depth-First Search,简称 DFS)是一种用于遍历或搜索树或图的算法。它从起始节点出发,沿着图的一个分支一路向下探索,直到到达最深的节点(即没有未访问的邻居节点为止),然后回溯到上一个节点,继续探索其他未被访问的分支,直到所有节点都被访问过为止。

在本文中,我们将详细解析如何在 Java 中实现 DFS,并讨论相关的原理。

1. 深度优先搜索的基本原理

DFS 算法的核心思想是从图的一个节点出发,尽可能深入探索图的分支。当遇到死胡同时,回溯到最近的分支,继续搜索其他分支,直到所有节点都被遍历一遍。

我们可以使用递归或者显式栈来实现 DFS。在这篇文章中,我们将使用递归方式来实现 DFS。

2. 程序实现

下面是一个简单的 Java 实现,展示了如何用 DFS 对图进行遍历。假设图是由邻接矩阵表示的,这意味着每个节点通过一个二维数组来存储与其他节点的连接关系。

2.1 图的表示与输入

首先,我们定义图的大小(最大顶点数为 20)并接收边的输入。图的数据结构是一个二维数组 graph,其中 graph[i][j] = 1 表示节点 i 与节点 j 有一条边,graph[i][j] = 0 表示没有边。

public static void main(String[] args) throws Exception {
    int n = nextInt(); // 顶点数
    graph = new int[n + 1][n + 1];
    int m = nextInt(); // 边数
    for (int i = 0; i < m; i++) {
        int s = nextInt();
        int e = nextInt();
        graph[s][e] = 1;
    }
}

在输入阶段,我们通过 nextInt() 方法接收数据。首先读取顶点数量 n,然后读取边的数量 m。对于每条边(由两个节点 se 定义),在邻接矩阵中更新对应的位置。

2.2 深度优先搜索的实现

DFS 的实现部分主要通过一个递归函数来完成。我们将维护一个 visited 数组来记录哪些节点已经被访问过,以防止重复遍历。

public static List<Integer> dfsByadList(int start) {
    List<Integer> ans = new ArrayList<>();
    boolean[] visited = new boolean[MAXN];
    dfs(start, visited, ans);
    return ans;
}

public static void dfs(int cur, boolean[] visited, List<Integer> ans) {
    visited[cur] = true;  // 标记当前节点为已访问
    ans.add(cur);         // 将当前节点加入结果列表
    for (int i = 0; i < graph.length; i++) {
        if (graph[cur][i] == 1 && !visited[i]) {  // 如果存在边且节点未访问
            dfs(i, visited, ans);  // 递归访问该节点
        }
    }
}
2.3 递归过程解析
  1. 开始访问 :首先从指定的起始节点 start 开始调用 dfs() 函数。每次递归调用时,都会将当前节点标记为已访问,并将其添加到结果列表 ans 中。

  2. 递归探索邻居节点 :然后,程序遍历当前节点的所有邻居(即从 graph[cur] 的每一列查找是否有边)。如果存在边且该邻居节点尚未被访问,就递归调用 dfs(),进一步深入探索。

  3. 回溯:当某个节点的所有邻居都已经访问过时,递归会回溯到上一个节点,继续寻找下一个未访问的邻居。

  4. 结束遍历 :当所有节点都被访问过后,递归最终会结束,所有节点都将被依次加入到 ans 列表中,代表 DFS 遍历的顺序。

2.4 输出结果

DFS 完成后,我们将遍历的节点顺序打印出来:

for (int num : ans) {
    out.print(num + " ");
}
out.println();
out.flush();
out.close();
br.close();

这个过程会打印出从起始节点开始的 DFS 遍历结果。

3. 深度优先搜索的时间复杂度

DFS 的时间复杂度为 O(V + E) ,其中 V 是图中节点的数量,E 是边的数量。每个节点和每条边最多会被访问一次,因此时间复杂度为 O(V + E)

  • V:每个节点最多被访问一次。
  • E:每条边最多被访问一次。
4. 总结

DFS 是一种经典的图遍历算法,适用于搜索图中的路径、连通性检测等问题。我们在本文中通过 Java 代码展示了如何实现一个深度优先搜索,并解释了 DFS 的工作原理与流程。通过递归方式实现,DFS 对于图的搜索非常高效,尤其在需要深入到图的某一分支时尤为有用。

以下是完整的 Java 代码,包括深度优先搜索(DFS)算法的实现和图的输入输出处理:

import java.io.*;
import java.util.*;

public class Main {

    public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    public static StreamTokenizer in = new StreamTokenizer(br);
    public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    static int MAXN = 20; // 最大节点数
    static int[][] graph;

    public static void main(String[] args) throws Exception {
        // 读取顶点数
        int n = nextInt(); 
        graph = new int[n + 1][n + 1]; // 图的邻接矩阵
        // 读取边数
        int m = nextInt(); 
        // 构建图的邻接矩阵
        for (int i = 0; i < m; i++) {
            int s = nextInt();  // 起点
            int e = nextInt();  // 终点
            graph[s][e] = 1;    // 设置邻接矩阵
        }
        
        // 输出邻接矩阵
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                out.print(graph[i][j] + " ");
            }
            out.println();
        }
        
        // 从节点1开始DFS遍历
        List<Integer> ans = dfsByadList(1); 
        // 输出DFS遍历结果
        for (int num : ans) {
            out.print(num + " ");
        }
        out.println();
        
        // 刷新并关闭流
        out.flush();
        out.close();
        br.close();
    }

    // 使用DFS遍历图,返回访问的节点顺序
    public static List<Integer> dfsByadList(int start) {
        List<Integer> ans = new ArrayList<>(); // 存储遍历结果
        boolean[] visited = new boolean[MAXN];  // 记录节点是否被访问
        dfs(start, visited, ans); // 从起点开始递归DFS
        return ans;
    }

    // DFS递归函数
    public static void dfs(int cur, boolean[] visited, List<Integer> ans) {
        visited[cur] = true; // 标记当前节点已访问
        ans.add(cur);        // 将当前节点添加到结果列表中
        for (int i = 0; i < graph.length; i++) {
            // 如果当前节点和i节点之间有边,并且i节点未被访问过
            if (graph[cur][i] == 1 && !visited[i]) {
                dfs(i, visited, ans); // 递归访问邻居节点i
            }
        }
    }

    // 获取下一个整数输入
    public static int nextInt() throws Exception {
        in.nextToken();
        return (int) in.nval;
    }
}
相关推荐
Joyner20183 分钟前
python-leetcode-从中序与后序遍历序列构造二叉树
算法·leetcode·职场和发展
因兹菜6 分钟前
[LeetCode]day9 203.移除链表元素
算法·leetcode·链表
LNsupermali7 分钟前
力扣257. 二叉树的所有路径(遍历思想解决)
算法·leetcode·职场和发展
雾月557 分钟前
LeetCode LCR180文件组合
算法·leetcode·职场和发展
萌の鱼7 分钟前
leetcode 2080. 区间内查询数字的频率
数据结构·c++·算法·leetcode
Tisfy8 分钟前
LeetCode 0541.反转字符串 II:模拟
算法·leetcode·字符串·题解
CM莫问2 小时前
什么是门控循环单元?
人工智能·pytorch·python·rnn·深度学习·算法·gru
查理零世2 小时前
【算法】回溯算法专题① ——子集型回溯 python
python·算法
labmem13 小时前
Leetcode:541
算法·leetcode·职场和发展