算法系列之搜索算法-深度优先搜索DFS

随着每年"金三银四"招聘季的到来,许多求职者开始积极备战面试。 在众多面试环节中,机试往往是不可或缺的一环,而算法能力更是机试考核的重点。 为此,我们特别推出算法系列文章,帮助大家系统复习算法知识。

随着每年"金三银四"招聘季的到来,许多求职者开始积极备战面试。在众多面试环节中,机试往往是不可或缺的一环,而算法能力更是机试考核的重点。为此,我们特别推出算法系列文章,帮助大家系统复习算法知识。今天,我们将首先探讨搜索算法中的重要内容——深度度优先搜索(DFS)。

图的介绍

图(Graph)是一种非线性的数据结构,由顶点(Vertex)和边(Edge)组成。如下图所示

_20250219193211.jpg分类如下:

  • 无向图(Undirected Graph):边没有方向,表示双向关系。
  • 有向图(Directed Graph):边有方向,表示单向关系。
  • 加权图(Weighted Graph):边带有权重。
  • 无权图(Unweighted Graph):边没有权重。

深度优先搜索(DFS, Depth-First Search)

深度优先搜索和广度优先搜索一样,都是对图进行搜索的算法,目的也都是从起点开始搜索,直到到达顶点。深度优先搜索会沿着一条路径不断的往下搜索,直到不能够在继续为止,然后在折返,开始搜索下一条候补路径。

DFS 可以借助于栈或者来实现。栈具有”后进先出(LIFO)”特性,可以是有栈或者递归来实现遍历。其实现步骤如下:

  1. 访问节点:从起始节点开始,访问当前节点。
  2. 递归
  • 递归访问邻居:对于当前节点的每一个未访问过的邻居节点,递归地调用 DFS。
  • 回溯:当没有未访问的邻居时,回溯到上一个节点,继续搜索其他路径。
  • 使用 Stack 来模拟递归过程,每次从栈中弹出一个节点并访问它,然后将未访问的邻居节点压入栈中。

示例代码如下:

复制
/**
 * 深度优先搜索示例
 */
public class DFSExample {
    // 定义图的节点类
    static class Node {
        int value;
        List< Node> neighbors;

        public Node(int value) {
            this.value = value;
            this.neighbors = new ArrayList<>();
        }

        // 添加邻接节点
        public void addNeighbor( Node neighbor) {
            this.neighbors.add(neighbor);
        }
    }

    /**
     * 方式一 :栈实现
     * dfs 函数
     * @param startNode
     */
    public static void dfs( Node startNode) {

        if(startNode == null ) return;
        // 使用队列存储待访问的节点
        Stack<Node> stack = new Stack<>();

        // 使用HashSet记录已访问的节点
        Set<Node> visited = new HashSet<>();
        // 将起点加入栈并标记为已访问
        stack.push(startNode);
        visited.add(startNode);
        // 遍历栈
        while (!stack.isEmpty()){
             Node currentNode = stack.pop();
            System.out.println(currentNode.value);
            // 遍历当前节点的所有邻接节点
            for (Node neighbor : currentNode.neighbors) {
                // 如果邻接节点未被访问,则加入栈并标记为已访问
                if (!visited.contains(neighbor)) {
                    stack.add(neighbor);
                    visited.add(neighbor);
                }
            }
        }

    }
    //方式二:递归实现
    public static void sec( Node currentNode,Set<Node> visited) {

        // 标记当前节点为已访问
        visited.add(currentNode);
        System.out.println(currentNode.value);
        // 递归访问所有未访问的邻居节点
        for (Node neighbor : currentNode.neighbors) {
            if (!visited.contains(neighbor))
                sec(neighbor, visited);
        }
    }

    /**
     *
     * @param args
     */
    public static void main(String[] args) {
        Node node1 = new  Node(1);
        Node node2 = new  Node(2);
        Node node3 = new  Node(3);
        Node node4 = new  Node(4);
        Node node5 = new  Node(5);
        Node node6 = new  Node(6);


        node1.addNeighbor(node2);
        node1.addNeighbor(node3);
        node1.addNeighbor(node5);

        node2.addNeighbor(node1);
        node2.addNeighbor(node3);
        node2.addNeighbor(node5);

        node3.addNeighbor(node1);
        node3.addNeighbor(node2);
        node3.addNeighbor(node4);
        node3.addNeighbor(node6);

        node4.addNeighbor(node3);
        node4.addNeighbor(node6);

        node5.addNeighbor(node2);
        node5.addNeighbor(node6);

        node6.addNeighbor(node3);
        node6.addNeighbor(node4);
        node6.addNeighbor(node5);
        //栈实现
        dfs(node1);
        System.out.println("+++++++++递归实现++++++++++++");
        //递归实现
        Set< Node> visited = new HashSet<>();
        sec(node1,visited);
    }
}

DFS的特点

  • 时间复杂度:O(V+E)
  • 空间复杂度:O(V)
  • 适用场景:连通性检测、路径查找、迷宫求解

DFS 示例题

以下列举了一些机试题

广播服务器

题目描述:给定一个大小为 N×N 的二维矩阵 matrix,表示 N 个服务器之间的连接情况。matrix[i][j] = 1 表示服务器i 和 j 直接连接,matrix[i][j] = 0 表示不直接连接。计算初始需要给几台服务器广播,才能使每个服务器都收到广播。 输入:N 行,每行 N 个数字(0 或 1),表示 N×N 的二维矩阵。 输出:需要广播的服务器的数量。

示例一: 输入: 

1 0 0 

0 1 0

 0 0 1 

输出:3

示例二: 输入: 

1 1 

1 1 

输出:1

复制
public class DFSServer {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String[] firstLine = scanner.nextLine().split(" ");
        int n = firstLine.length;
        //初始化二维数组
        int[][] matrix = new int[n][n];
        for (int i = 0; i < n; i++) {
            String[] line = i==0 ? firstLine:scanner.nextLine().split(" ");
            for (int j = 0; j < n; j++) {
                matrix[i][j] = Integer.parseInt(line[j]);
            }
        }
        //初始化标记数组
        int[] check = new int[n];
        //需要广播的服务器的数量
        int ans = 0;
        Stack<Integer> stack = new Stack<>();
        // 遍历每个服务器
        for (int i = 0; i < n; i++) {
            // 如果服务器 i 没有被访问过,就以它为起点进行 DFS
            if (check[i] == 0) {
                ans++; // 新的连通分量,计数器加 1
                stack.add(i); // 将当前服务器加入栈
                check[i] = 1; // 标记为已访问

                // 开始 DFS
                while (!stack.isEmpty()) {
                    // 弹出当前服务器
                    int cur = stack.pop();
                    // 遍历所有服务器,找到与 cur 相连且未访问的服务器
                    for (int j = 0; j < n; j++) {
                        if (j != cur && matrix[cur][j] == 1 && check[j] == 0) {
                            // 将未访问的服务器加入栈
                            stack.push(j);
                            // 将未访问的服务器加入栈
                            check[j] = 1;
                        }
                    }
                }
            }
        }

        System.out.println(ans);

    }
}

总结

DFS 是一种非常重要的图遍历算法,适用于许多场景。递归实现简单直观,但可能会受到栈深度的限制;迭代实现则避免了这个问题,但代码稍复杂一些。根据具体需求选择合适的实现方式。

相关资讯

超越思维链?深度循环隐式推理引爆AI圈,LLM扩展有了新维度

不需要特殊训练数据,能处理语言难以形容的任务,3.5B 参数能实现 50B 的性能。 这是一种全新的语言模型架构,能够通过使用循环语言模型在潜在空间中隐式推理,显著提升模型的计算效率,尤其是在需要复杂推理的任务上。 近日,马里兰大学的一篇论文在 AI 研究社区中引发了关注,其提出的语言模型通过迭代循环块来工作,能在测试时展开到任意深度。

天才解法震惊人类!谷歌AI破天荒摘得奥数金牌,横扫IMO 42道几何难题

时隔6个多月,AlphaGeometry 2直接攻下IMO金牌! 刚刚,谷歌DeepMind一篇28页技术报告,公布了AG2最新突破——在2000-2024年IMO几何题上,解题率从54%飙升至84%。 论文地址:(50道),AG2横扫了42道。

如何优化测试时计算?解决「元强化学习」问题

优化大模型的测试时计算是提升模型部署效率和节省计算资源的关键一环。 前段时间,黄仁勋在 CES 2025 的演讲中把测试时 Scaling 形容为大模型发展的三条曲线之一。 如何优化测试时计算成为业界关注的重要课题。