Go语言实战案例-深度优先遍历DFS

一、什么是深度优先遍历(DFS)?

在图、树等数据结构中,**深度优先遍历(DFS)**是一种重要的遍历方式,其核心思想是"一条路走到底",在每个分支尽可能深入,直到无法继续再回溯。

在树结构中,DFS 一般有三种形式:

  • 前序遍历(Pre-order):根 → 左 → 右
  • 中序遍历(In-order):左 → 根 → 右
  • 后序遍历(Post-order):左 → 右 → 根

在图结构中,DFS 可用于:

  • • 寻找路径
  • • 连通分量计数
  • • 拓扑排序
  • • 拓展岛屿问题、迷宫路径等经典题

二、DFS vs BFS 区别一览

特性 DFS(深度优先) BFS(广度优先)
结构 使用栈或递归 使用队列
遍历方式 一条路走到底 分层遍历,按距离推进
空间复杂度 O(h),h为深度 O(w),w为最大宽度
应用场景 拓扑排序、岛屿数量等 最短路径、层序遍历

三、DFS在树结构中的应用

示例:二叉树的前序遍历

go 复制代码
type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

方法一:递归实现

scss 复制代码
func preorderTraversal(root *TreeNode) []int {
    var result []int
    var dfs func(node *TreeNode)

    dfs = func(node *TreeNode) {
        if node == nil {
            return
        }
        result = append(result, node.Val) // 根
        dfs(node.Left)                    // 左
        dfs(node.Right)                   // 右
    }

    dfs(root)
    return result
}

方法二:非递归实现(使用栈)

go 复制代码
func preorderIterative(root *TreeNode) []int {
    if root == nil {
        return nil
    }

    stack := []*TreeNode{root}
    var result []int

    for len(stack) > 0 {
        node := stack[len(stack)-1]
        stack = stack[:len(stack)-1]

        result = append(result, node.Val)

        // 注意:先右后左入栈
        if node.Right != nil {
            stack = append(stack, node.Right)
        }
        if node.Left != nil {
            stack = append(stack, node.Left)
        }
    }

    return result
}

四、DFS在图结构中的应用

图定义:

go 复制代码
type Graph struct {
    adj map[int][]int
}

func NewGraph() *Graph {
    return &Graph{adj: make(map[int][]int)}
}

func (g *Graph) AddEdge(u, v int) {
    g.adj[u] = append(g.adj[u], v)
    g.adj[v] = append(g.adj[v], u) // 无向图
}

DFS遍历图(递归版)

go 复制代码
func DFS(g *Graph, start int, visited map[int]bool) {
    if visited[start] {
        return
    }

    fmt.Println("访问节点:", start)
    visited[start] = true

    for _, neighbor := range g.adj[start] {
        if !visited[neighbor] {
            DFS(g, neighbor, visited)
        }
    }
}

示例调用:

css 复制代码
func main() {
    g := NewGraph()
    g.AddEdge(0, 1)
    g.AddEdge(0, 2)
    g.AddEdge(1, 3)
    g.AddEdge(1, 4)
    g.AddEdge(2, 5)

    visited := make(map[int]bool)
    DFS(g, 0, visited)
}

输出顺序可能为:0 -> 1 -> 3 -> 4 -> 2 -> 5(视边顺序而定)


五、常见应用场景

应用场景 描述
二叉树遍历 前序、中序、后序遍历
图搜索 查找路径、连通分量、是否有环等
岛屿数量 类似图搜索,常用于二维数组(栅格图)处理
排列组合 回溯算法基础实现,搜索树就是 DFS
迷宫问题 从起点探索到终点路径

六、经典面试题与变种

    1. 岛屿数量问题(LeetCode 200)
      用 DFS 解决二维矩阵中有多少块"1"组成的岛屿;
    1. 路径总和(LeetCode 112)
      用 DFS 判断是否存在一条路径,其路径和等于给定值;
    1. 括号生成(LeetCode 22)
      回溯(基于 DFS)生成所有合法括号组合;
    1. 图是否有环、是否为树
      用 DFS 标记访问状态;

七、深度优先 vs 回溯算法

DFS 是回溯算法的基础。回溯是在 DFS 的基础上加上"状态恢复"的机制,在遍历路径中做出选择 → 递归探索 → 撤销选择,常用于组合、排列、子集等问题。


八、复杂度分析

对于 DFS:

  • 树结构
    • • 时间复杂度:O(n)
    • • 空间复杂度:O(h)(递归栈,h 为高度)
  • 图结构
    • • 时间复杂度:O(V + E)(节点+边)
    • • 空间复杂度:O(V)(访问记录)

九、调试建议与注意事项

  • • DFS中需要防止死循环,特别是图结构中需要维护 visited;
  • • 栈深过大时可能栈溢出,考虑改为迭代;
  • • 图中有环需要特别注意:判断前访问标记;
  • • 二叉树中 DFS 可用于路径、结构、镜像判断等。

十、总结

点位 内容说明
算法本质 基于栈的深度遍历策略
常见应用 树遍历、图搜索、回溯组合、路径搜索等
实现方式 递归或显式使用栈
注意点 图需维护访问标记,避免死循环
与BFS比较 更适合探索所有路径、搜索深层解空间

相关推荐
岁忧5 小时前
(nice!!!)(LeetCode 每日一题) 679. 24 点游戏 (深度优先搜索)
java·c++·leetcode·游戏·go·深度优先
你的人类朋友7 小时前
说说git的变基
前端·git·后端
阿杆7 小时前
玩转 Amazon ElastiCache 免费套餐:小白也能上手
后端
阿杆7 小时前
无服务器每日自动推送 B 站热门视频
后端
公众号_醉鱼Java8 小时前
Elasticsearch 字段膨胀使用 Flattened类型
后端·掘金·金石计划
JohnYan9 小时前
工作笔记 - CentOS7环境运行Bun应用
javascript·后端·容器
探索java9 小时前
Netty Channel详解:从原理到实践
java·后端·netty
追逐时光者9 小时前
2025 年全面的 C#/.NET/.NET Core 学习路线集合,学习不迷路!
后端·.net
ankleless11 小时前
Spring Boot 实战:从项目搭建到部署优化
java·spring boot·后端
百锦再11 小时前
一文精通 Swagger 在 .NET 中的全方位配置与应用
后端·ui·.net·接口·配置·swagger·访问