go语言需手动实现dfs/bfs,用切片模拟栈/队列,避免range动态扩容错误;邻接表更符合go习惯;bfs需显式维护层数、dist初始化为-1或用map判存,dfs须双重检查nil与visited。

Go 语言本身不提供 dfs 或 bfs 内置函数,所谓“通过语言学习实现”,本质是用 Go 的基础语法(切片、map、for、递归)模拟栈和队列行为——不是调用库,而是亲手搭轮子。
为什么不能直接 for range queue 做 BFS
常见错误是写成:for _, node := range queue { ... queue = append(queue, child) ... }。这会导致遍历过程中 queue 动态增长,range 会按初始长度迭代,但新入队节点被跳过,或下一轮又重复处理旧节点。
- 正确做法:每次循环前先记
size := len(queue),再用for i := 0; i 处理当前层 - 出队必须用
queue = queue[1:](切片截断),不是queue = queue[:len(queue)-1] - 树结构可省
visited,但只要用邻接表表示图(哪怕只有父子关系),就必须加map[int]bool,否则 A→B→A 会死循环
DFS 递归 vs 手写栈:选哪个看深度
Go 默认 goroutine 栈仅 2KB,递归 DFS 在深链图(如 1000+ 节点单向链)中极易触发 runtime: goroutine stack exceeds 1000000000-byte limit。
- 适合递归:二叉树遍历、BST 查找、路径回溯(
path切片传指针 + 回退操作) - 必须手写栈:图节点数 > 500,或已知存在长链;用
stack := []*Node{root},出栈为pop := stack[len(stack)-1]; stack = stack[:len(stack)-1] - 无论哪种,
visited都不能少——DFS 不像 BFS 天然保序,漏标一个节点就可能反复访问
图的表示:邻接表比邻接矩阵更 Go
邻接矩阵([][]bool)查边是 O(1),但空间是 O(n²),且 Go 中二维切片初始化麻烦、扩容成本高;真实项目几乎都用邻接表。
- 节点 ID 连续(0~n-1):用
adj := make([][]int, n),性能好、无哈希开销 - 节点 ID 稀疏或非数字(如字符串):用
adj := make(map[string][]string),灵活但有哈希成本 - 无向图务必双向加边:
adj[u] = append(adj[u], v)和adj[v] = append(adj[v], u),漏一边等于删掉半张图
BFS 求最短距离时,dist 数组怎么初始化才安全
用 dist := make([]int, n) 全填 0 是错的——无法区分“起点距离为 0”和“节点不可达”。同样,make(map[int]int) 默认零值也是 0,也会混淆。
- 推荐方案一:
dist := make([]int, n),初始化全为-1(for i := range dist { dist[i] = -1 }),更新时只对未访问节点赋值 - 推荐方案二:
dist := make(map[int]int),不预设任何值,查时用if d, ok := dist[node]; ok判断是否可达 - 别在 BFS 循环里反复
make([]int, n)——高频分配小切片会触发 GC,尤其在服务端长期运行场景
最容易被忽略的是:BFS 的“层”意识必须显式维护,不能依赖数据结构自动分层;DFS 的递归退出条件常被写成 node == nil,但在图中要同时检查 !visited[node],否则逻辑就垮了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











