遍历搜索算法
图遍历是图算法的基础,主要有两种遍历方式:广度优先搜索(BFS)和深度优先搜索(DFS)。这两种算法在树、图的遍历、路径查找、连通性判断等问题中都有广泛应用。
广度优先搜索(BFS)
基本概念
广度优先搜索(Breadth-First Search,BFS)是一种按层次遍历的算法,从起始节点开始,先访问所有距离为 1 的节点,再访问距离为 2 的节点,以此类推。
核心特点
- 使用队列:先进先出(FIFO)的数据结构
- 层次遍历:按距离起始节点的远近逐层访问
- 最短路径:在无权图中,BFS 能找到从起点到终点的最短路径
- 空间复杂度较高:需要存储每一层的所有节点
算法流程
- 将起始节点加入队列并标记为已访问
- 当队列不为空时:
- 取出队首节点
- 访问该节点的所有未访问邻居节点
- 将邻居节点加入队列并标记为已访问
- 重复步骤 2 直到队列为空
Golang 实现
基础 BFS 实现
1 | package main |
二维网格 BFS(LeetCode 常见题型)
1 | // LeetCode 200. 岛屿数量(BFS 版本) |
BFS 应用场景
- 最短路径问题:无权图中的最短路径
- 层次遍历:树的层序遍历、图的层次遍历
- 连通性判断:判断图中节点是否连通
- 最小生成树:Prim 算法的实现
- 拓扑排序:有向无环图的拓扑排序
深度优先搜索(DFS)
基本概念
深度优先搜索(Depth-First Search,DFS)是一种沿着树的深度遍历的算法,尽可能深地搜索树的分支。当节点 v 的所有边都已被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。
核心特点
- 使用栈:后进先出(LIFO)的数据结构,或递归实现
- 深度遍历:尽可能深地搜索,直到无法继续再回溯
- 空间复杂度较低:只需要存储当前路径上的节点
- 可能不是最短路径:DFS 找到的路径不一定是最短的
算法流程
- 从起始节点开始,标记为已访问
- 访问该节点的第一个未访问邻居节点
- 递归访问该邻居节点
- 当没有未访问的邻居时,回溯到上一个节点
- 重复步骤 2-4 直到所有节点都被访问
Golang 实现
递归实现(推荐)
1 | package main |
二维网格 DFS
1 | // LeetCode 200. 岛屿数量(DFS 版本) |
DFS 应用场景
- 连通性判断:判断图中节点是否连通
- 拓扑排序:有向无环图的拓扑排序
- 强连通分量:Tarjan 算法、Kosaraju 算法
- 回溯算法:八皇后、数独、全排列等
- 路径查找:查找所有可能的路径
- 树的遍历:前序、中序、后序遍历
BFS vs DFS 对比
核心区别
| 特性 | BFS(广度优先) | DFS(深度优先) |
|---|---|---|
| 数据结构 | 队列(Queue) | 栈(Stack)或递归 |
| 遍历方式 | 层次遍历,逐层访问 | 深度遍历,尽可能深入 |
| 最短路径 | 在无权图中能找到最短路径 | 不一定是最短路径 |
| 空间复杂度 | O(b^d),b 是分支因子,d 是深度 | O(b×d) |
| 时间复杂度 | O(V + E) | O(V + E) |
| 实现方式 | 通常用迭代 | 通常用递归 |
| 适用场景 | 最短路径、层次遍历 | 回溯、连通性判断 |
选择建议
使用 BFS:
- 需要找最短路径
- 需要层次遍历
- 图的深度很大但宽度较小
使用 DFS:
- 需要回溯(如排列组合问题)
- 需要遍历所有路径
- 图的宽度很大但深度较小
- 内存有限时(DFS 空间复杂度更低)
时间复杂度分析
时间复杂度:O(V + E),其中 V 是节点数,E 是边数
- 每个节点访问一次:O(V)
- 每条边检查一次:O(E)
空间复杂度:
- BFS:O(V),最坏情况需要存储所有节点
- DFS:O(h),其中 h 是最大深度,递归栈的深度
实际应用示例
1. 树的层序遍历(BFS)
1 | // LeetCode 102. 二叉树的层序遍历 |
2. 回溯算法(DFS)
1 | // LeetCode 46. 全排列(DFS 回溯) |
3. 图的连通分量(DFS)
1 | // 计算无向图中的连通分量数量 |
总结
BFS 和 DFS 是图遍历的两种基本方法,各有优缺点:
- BFS 适合找最短路径、层次遍历等问题
- DFS 适合回溯、连通性判断、遍历所有路径等问题
在实际应用中,要根据具体问题选择合适的算法。有时也可以结合使用,如双向 BFS 可以更快地找到最短路径。
参考文献
- LeetCode 图遍历专题
- 《算法导论》第 22 章
- 《数据结构与算法分析》