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比较 更适合探索所有路径、搜索深层解空间

相关推荐
进击的铁甲小宝42 分钟前
Django-environ 入门教程
后端·python·django·django-environ
掘金码甲哥1 小时前
Go动态感知资源变更的技术实践,你指定用过!
后端
王柏龙2 小时前
ASP.NET Core MVC中taghelper的ModelExpression详解
后端·asp.net·mvc
无限大62 小时前
算法精讲:二分查找(二)—— 变形技巧
后端
勇哥java实战分享2 小时前
基于 RuoYi-Vue-Pro 定制了一个后台管理系统 , 开源出来!
后端
超浪的晨4 小时前
JavaWeb 入门:JavaScript 基础与实战详解(Java 开发者视角)
java·开发语言·前端·javascript·后端·学习·个人开发
KubeSphere4 小时前
eBPF 赋能云原生: WizTelemetry 无侵入网络可观测实践
后端
codervibe4 小时前
从毕业设计到企业级模板:一次 Spring Boot 后端项目目录结构重构实录
后端·github
云边散步4 小时前
《校园生活平台从 0 到 1 的搭建》第五篇:商品后端
前端·后端