Go图遍历首选邻接表,因其省内存、契合切片操作;BFS需用切片模拟队列并记录当前层长度;visited必须按场景初始化且每次调用前重置。

Go 里写图遍历,别急着敲 DFS 或 BFS 函数——先确认图用的是邻接表,否则后续所有逻辑都可能白忙。
为什么邻接表是 Go 图实现的默认选择
Go 没有内置图类型,你得自己选底层结构。邻接矩阵([][]int)查边快但空间吃紧:1000 个节点就要 100 万 int;而真实业务图(社交关系、依赖链、网页链接)全是稀疏的——边数远小于节点数平方。
邻接表用 map[int][]int 或 []([]int) 表示,省内存、遍历邻居自然,且和 Go 的切片操作天然是契合的。知识库中所有可运行示例(包括带 AddEdge 方法的 Graph 结构)都基于邻接表。
容易踩的坑:
立即学习“go语言免费学习笔记(深入)”;
- 用邻接矩阵初始化后忘了预分配大小,导致频繁扩容拖慢
BFS入队速度 - 误把树的邻接表逻辑直接套到无向图上,漏掉双向建边(
AddEdge(v1, v2)必须同时往v1和v2的邻居列表里加对方) - 节点 ID 类型混用:整数 ID 用
map[int]bool做visited,字符串 ID 却还用map[int]bool,结果永远不命中
BFS 必须用切片模拟队列,且要记当前层长度
BFS 的本质是“按层推进”,不是“边遍历边追加”。Go 没有内置队列,用切片最实际:queue = queue[1:] 出队,append(queue, node) 入队。
关键动作是每次进入 for 循环前先存下 size := len(queue),然后只处理这 size 个节点。否则在循环里直接 range queue,会因 append 导致切片底层数组扩容、迭代器越界或漏掉新入队节点。
常见错误现象:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 输出顺序乱序,比如本该 [A,B,C] 却变成 [A,B,A,C]
- 无限循环,尤其在无向图中没加
visited判断时,A→B→A→B 反复跳 - 无法区分层级,导致没法做“最小步数”类需求(如迷宫最短路径)
正确写法核心节选:
queue := []int{start}
visited[start] = true
for len(queue) > 0 {
size := len(queue)
for i := 0; i < size; i++ {
v := queue[0]
queue = queue[1:]
// 处理 v
for _, n := range g.vertices[v] {
if !visited[n] {
visited[n] = true
queue = append(queue, n)
}
}
}
}
DFS 递归写法简洁,但深图必须防栈溢出
递归版 DFS 代码干净:dfsHelper 一路往下,回溯靠函数调用栈。但 Go 默认 goroutine 栈只有 2KB,遇到长链(比如 1000 层嵌套)或环检测失败时,直接 panic: runtime: goroutine stack exceeds 1000000000-byte limit。
生产环境建议两种方案选一:
- 改用手写栈(
stack := []int{start}),用pop := stack[len(stack)-1]; stack = stack[:len(stack)-1]模拟 LIFO - 保留递归,但加深度限制参数(
maxDepth int),并在递归入口判断depth > maxDepth就提前返回
另一个易错点:递归版若漏标 visited[v] = true,或标在 for 循环之后(而非入口处),会导致同一节点被多次压栈,最终爆栈或重复访问。
图遍历中 visited 初始化与使用场景强相关
visited 不是摆设,它直接决定算法是否收敛。树结构可以省略,因为父子关系单向;但只要用邻接表表示图(哪怕原先是树),就必须加 visited,否则无向图必然死循环。
初始化方式要看场景:
- 固定整数节点 ID(0~n-1):用
visited := make([]bool, n),索引即 ID,O(1) 访问 - 动态 ID(字符串、负数、稀疏编号):用
visited := make(map[any]bool),避免大数组浪费 - 需要记录距离/步数:用
dist := make(map[int]int),初始值设为 -1 表示未访问,比bool更信息丰富
最容易被忽略的是:多个独立调用共用同一个 visited map 而没重置,导致第二次 BFS 直接跳过所有节点——每次调用前必须 make 新的 visited 或手动清空。


















