dfs遍历图必须手动维护visited状态,用map[string]bool避免重复访问和无限递归;无向/有向图均依赖visited防环;推荐切片模拟栈并返回bool早停;大节点数时map更省内存。

用 dfs 递归函数遍历图,必须手动维护 visited 状态
Go 没有内置的图遍历库,dfs 不是语言关键字,而是你要自己写的函数。最常见错误是忘记传入或更新 visited map,导致无限递归或重复访问。
-
visited必须是引用传递(即 map 类型本身是引用),但如果你在递归中重新赋值整个 map 变量(比如visited = make(map[int]bool)),子调用就看不到父层的标记 - 节点类型不限于
int;用string做键更贴近真实场景(如城市名、API 路径),此时visited map[string]bool更安全 - 无向图需双向加边,但 DFS 过程中只靠
visited防重,不依赖边方向;有向图同理,无需额外判断“能否回头”
非递归 DFS 必须手写栈,别依赖 container/list
用 container/list 实现栈看似省事,但它的接口是通用链表,每次 PushBack/Remove 都有额外内存分配和指针跳转开销;而真实项目中 DFS 深度可能上千,栈操作频次极高。
- 推荐用切片模拟栈:
stack := []int{start},入栈用stack = append(stack, v),出栈用v := stack[len(stack)-1]; stack = stack[:len(stack)-1] - 注意:切片栈的
pop必须先取后裁,顺序反了会 panic 或读到脏数据 - 如果节点带元数据(如路径、深度),栈元素应为结构体,而非裸值;例如
type stackItem struct { node string; depth int }
dfs 搜索目标节点时,递归返回值要早停,不能只靠 return
很多初学者写搜索只关心“找到了没”,却忽略一旦找到就该立刻终止所有递归分支——否则程序仍会继续遍历无关子树,浪费 CPU 和栈空间。
- 递归函数应返回
bool,每层检查子调用返回值;若为true,立即return true,不执行后续邻居循环 - 不要用全局变量或闭包捕获结果来“中断”,那会让逻辑分散且难以测试
- 示例关键片段:
if dfs(graph, visited, neighbor, target) { return true }
从树 DFS 切换到图 DFS,最容易漏掉的是环检测逻辑
二叉树天然无环,所以 dfs(node.Left) 里根本不用想“会不会回到根”;但图里 A→B→C→A 是合法输入,没有 visited 就直接死循环。
- 环检测不是可选项,是图 DFS 的基础前提;哪怕你确定输入无环,也得保留
visited判断,否则代码不具备鲁棒性 - 对有向图做拓扑排序等高级用途时,还需区分 “临时访问中”(gray)和 “已访问完”(black)状态,仅用
bool不够 - 如果图节点数超大(如 10⁵ 级别),用
map存visited比用切片索引更省内存,尤其节点 ID 稀疏时
visited 竞态、字符串节点哈希冲突——这些不是理论风险,是上线后真会卡住服务的日志里的错误。写 dfs 最容易高估自己对“控制流”的掌握程度。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











