深度优先搜索(DFS)故事:从迷宫探险到家谱寻根

113 阅读4分钟

一、故事场景:迷宫中的勇往直前

小明又被困在了一个迷宫里,但这次他选择了不同的策略:

  1. 选择一个方向:比如一直向右走

  2. 勇往直前:遇到岔路时,选择第一个看到的方向继续走,不回头

  3. 遇到死胡同就回溯:如果走不通,回到上一个岔路口,尝试下一个方向

  4. 标记已走过的路:在墙上画箭头,避免重复走

这就是 ** 深度优先搜索(DFS)** 的核心思想:一条路走到黑,走不通再回头

二、DFS 的核心概念

  • 递归(或栈) :用递归函数(或显式栈)来实现回溯机制
  • 访问标记:记录已访问的节点,避免重复访问
  • 回溯:当当前路径走不通时,返回上一步继续探索

三、Java 代码实现:迷宫搜索

java

public class DepthFirstSearch {
    // 迷宫搜索示例
    public static void main(String[] args) {
        // 迷宫:0表示通路,1表示墙
        int[][] maze = {
            {0, 1, 0, 0, 0},
            {0, 1, 0, 1, 0},
            {0, 0, 0, 0, 0},
            {0, 1, 1, 1, 0},
            {0, 0, 0, 1, 0}
        };
        
        int startX = 0, startY = 0;  // 起点坐标
        int endX = 4, endY = 4;      // 终点坐标
        
        // 创建访问标记数组
        boolean[][] visited = new boolean[maze.length][maze[0].length];
        
        // 调用DFS方法搜索路径
        boolean found = dfs(maze, startX, startY, endX, endY, visited);
        
        if (found) {
            System.out.println("恭喜!找到出口了!");
        } else {
            System.out.println("遗憾!没有找到出口。");
        }
    }
    
    public static boolean dfs(int[][] maze, int x, int y, int endX, int endY, boolean[][] visited) {
        // 检查坐标是否越界或为墙或已访问
        if (x < 0 || x >= maze.length || y < 0 || y >= maze[0].length 
            || maze[x][y] == 1 || visited[x][y]) {
            return false;
        }
        
        // 标记当前位置为已访问
        visited[x][y] = true;
        
        // 检查是否到达终点
        if (x == endX && y == endY) {
            return true;
        }
        
        // 定义四个方向:上、下、左、右
        // 注意:这里的顺序会影响搜索路径的选择
        int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        
        // 尝试所有方向
        for (int[] dir : directions) {
            int newX = x + dir[0];
            int newY = y + dir[1];
            
            // 递归搜索新位置
            if (dfs(maze, newX, newY, endX, endY, visited)) {
                return true;  // 找到路径,立即返回
            }
        }
        
        // 所有方向都走不通,回溯
        return false;
    }
}

四、DFS 的执行过程详解

  1. 初始化

    • 从起点(0,0)开始
    • 标记起点为已访问
  2. 递归深入

    • 尝试向右走(0,1),是墙,失败
    • 尝试向下走(1,0),是通路,继续递归
    • (1,0)尝试向下走(2,0),继续递归
    • (2,0)尝试向右走(2,1),继续递归
    • (2,1)尝试各个方向,均失败,回溯到(2,0)
  3. 回溯与重试

    • (2,0)尝试向下走(3,0),继续递归
    • 重复上述过程,直到找到终点或所有路径都探索完毕

五、递归 vs 显式栈

上面的代码使用了递归实现 DFS,也可以用显式栈来模拟:

java

import java.util.Stack;

public class DepthFirstSearchWithStack {
    public static boolean dfsWithStack(int[][] maze, int startX, int startY, int endX, int endY) {
        int rows = maze.length;
        int cols = maze[0].length;
        boolean[][] visited = new boolean[rows][cols];
        Stack<int[]> stack = new Stack<>();
        
        // 将起点压入栈
        stack.push(new int[]{startX, startY});
        visited[startX][startY] = true;
        
        // 定义四个方向
        int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        
        while (!stack.isEmpty()) {
            int[] current = stack.pop();
            int x = current[0];
            int y = current[1];
            
            // 检查是否到达终点
            if (x == endX && y == endY) {
                return true;
            }
            
            // 尝试所有方向(注意:栈是后进先出,所以方向顺序要反过来)
            for (int i = directions.length - 1; i >= 0; i--) {
                int newX = x + directions[i][0];
                int newY = y + directions[i][1];
                
                if (newX >= 0 && newX < rows && newY >= 0 && newY < cols 
                    && maze[newX][newY] == 0 && !visited[newX][newY]) {
                    
                    stack.push(new int[]{newX, newY});
                    visited[newX][newY] = true;
                }
            }
        }
        
        return false;
    }
}

六、DFS vs BFS:不同的搜索策略

维度深度优先搜索(DFS)广度优先搜索(BFS)
数据结构栈(递归或显式栈)队列(Queue)
搜索顺序一条路走到黑,回溯再探索逐层扩展,先近后远
适用场景所有路径探索、连通性检查最短路径、最小步数问题
空间复杂度O (H)(H 为最大深度)O (V)(最坏情况下存储整层节点)

七、DFS 的其他应用场景

  1. 家谱寻根:查找家族中某人的所有后代
  2. 游戏 AI:探索游戏地图的所有可能路径
  3. 拓扑排序:在有向无环图中进行任务排序
  4. 连通分量:找出图中所有独立的连通区域

八、总结:DFS 的核心思想

DFS 就像在迷宫中勇往直前:

  1. 一条路走到黑:从起点开始,沿着一条路径尽可能深入

  2. 回溯机制:当遇到死胡同时,返回上一个岔路口,尝试其他方向

  3. 递归或栈:用递归函数或显式栈来管理回溯过程

掌握 DFS,就像掌握了在复杂网络中探索所有可能路径的方法,是算法学习的重要基础。