文章目录
1. 前言
在图论和算法领域,深度优先搜索(DFS)和广度优先搜索(BFS)是两种基础而强大的遍历策略。它们就像探索未知世界的两种不同性格的冒险家:DFS像一位执着的前行者,一条路走到底再回头;BFS则像一位谨慎的规划者,一层一层向外扩展。本文将深入探讨这两种算法的原理、实现和应用,帮助你全面理解它们的异同点。
2. 深度优先搜索 (DFS - Depth-First Search)
- 核心思想: “一条路走到黑,碰壁再回头”。
- 工作流程:
1. 从起始节点开始。
2. 随机选择一个相邻的未访问节点,深入访问它。
3. 对新访问的节点重复步骤2(递归或使用栈),不断向深处探索。
4. 当到达一个没有未访问相邻节点的节点(死胡同)时,回溯到最近一个有未访问相邻节点的节点。
5. 重复步骤2-4,直到所有节点都被访问。 - 数据结构: 使用栈 (Stack)(显式栈或递归调用时的隐式函数调用栈)。
- 特点:
- 空间复杂度较低: 通常只需要存储从根到当前节点的路径。在树或分支因子较小的图上空间复杂度为 O(h)(h 为最大深度),在一般图上最坏为 O(V)(V 为顶点数)。
- 可能找到非最短路径: 它找到的解路径不一定是最短路径。
- 实现简单(递归): 递归实现非常直观。
- 适用于:
- 寻找所有解或路径是否存在(如迷宫求解)。
- 拓扑排序。
- 检测图中的环。
- 查找连通分量。
- 解决某些需要探索所有可能性的问题(如八皇后、数独)。
- 图的强连通分量(Kosaraju 或 Tarjan 算法)。
3. 广度优先搜索 (BFS - Breadth-First Search)
- 核心思想: “层层递进,由近及远”。
- 工作流程:
1. 从起始节点开始,将其放入队列。
2. 从队列中取出一个节点进行访问。
3. 将这个节点的所有未访问的相邻节点放入队列。
4. 重复步骤2和3,直到队列为空。
5. 数据结构: 使用队列 (Queue)。 - 特点:
- 找到最短路径(未加权图): 当图的边没有权重(或权重都相等)时,BFS 从起点开始按层遍历,首次访问到某个节点所经过的路径就是最短路径。
- 空间复杂度较高: 在最坏情况下(如完全图),队列需要存储几乎所有的节点,空间复杂度为 O(V)。
- 适用于:
- 查找未加权图中的最短路径(最重要应用)。
- 查找所有节点到起点的最短距离(同样在未加权图中)。
- 网络爬虫(按层级抓取网页)。
- 广播网络(如洪水填充算法、寻找最短广播路径)。
- 垃圾回收中的可达性分析。
- 点对点网络查找邻居节点。
- 测试图的二分性。
4. 关键对比总结
特性 | 深度优先搜索 (DFS) | 广度优先搜索 (BFS) |
---|---|---|
核心策略 | 深度优先 (纵向探索) | 广度优先 (横向探索) |
数据结构 | 栈 (递归/显式栈) | 队列 |
空间效率 | 通常较低 (O(h) 或 O(V)) | 通常较高 (O(V), 最坏接近 O(V)) |
最短路径 | 不一定找到最短路径 | 在未加权图中保证找到最短路径 |
访问顺序 | 无固定顺序,取决于实现和选择策略 | 按距离起点层级递增的顺序访问 |
适用场景 | 路径存在性、所有解、拓扑排序、环检测 | 未加权图最短路径、层级遍历、广播、爬虫 |
类比 | 走迷宫 (选一条路走到头再试下一条) | 水面涟漪扩散 (一圈一圈向外) |
5. 测试用例
A
/ \
B C
/ \ \
D E F
DFS 遍历顺序 (假设先左后右): A -> B -> D -> E -> C -> F
- 从 A 开始,深入访问左子树 B。
- 在 B,深入访问左孩子 D(尽头)。
- 回溯到 B,访问右孩子 E(尽头)。
- 回溯到 A,访问右子树 C。
- 在 C,深入访问右孩子 F(尽头)。
BFS 遍历顺序: A -> B -> C -> D -> E -> F
- 访问 A (层0)。
- 访问 A 的邻居 B, C (层1)。
- 访问 B 的邻居 D, E (层2)。
- 访问 C 的邻居 F (层2)。
基础图数据结构
struct GraphNode {
char val;
std::vector<GraphNode *> neighbors;
GraphNode(char v = '\0') : val(v) {}
};
6. 深度优先搜索 (DFS) C++代码实现
void dfs(GraphNode *root, std::vector<char> &result,
std::set<GraphNode *> &visited) {
if (!root) {
return;
}
visited.insert(root);
result.push_back(root->val);
for (auto neighbor : root->neighbors) {
if (visited.find(neighbor) == visited.end()) {
visited.insert(neighbor);
dfs(neighbor, result, visited);
}
}
}
std::vector<char> dfs(GraphNode *root) {
std::set<GraphNode *> visited;
std::vector<char> result;
dfs(root, result, visited);
return result;
}
7. 广度优先搜索 (BFS) C++代码实现
std::vector<char> bfs(GraphNode *root) {
std::vector<char> result;
if (!root) {
return result;
}
std::queue<GraphNode *> q;
std::set<GraphNode *> visited;
visited.insert(root);
q.push(root);
while (!q.empty()) {
auto current = q.front();
q.pop();
result.push_back(current->val);
for (auto neighbor : current->neighbors) {
if (visited.find(neighbor) == visited.end()) {
visited.insert(neighbor);
q.push(neighbor);
}
}
}
return result;
}
测试主函数代码:
GraphNode *initGraph(const std::string &str) {
int size = str.size();
if (size == 0) {
return nullptr;
}
int idx = 0;
char ch = str[idx++];
GraphNode *root = new GraphNode(ch);
ch = str[idx++];
if (ch == '(') {
do {
if (ch == ',') {
ch = str[idx++];
continue;
}
auto sstr = substr(str, idx);
root->neighbors.push_back(initGraph(sstr));
ch = str[idx];
} while (idx < size);
}
return root;
}
void printResult(const std::vector<char> &result,
const std::string &perfix = "") {
if (perfix.size() > 0) {
std::cout << perfix;
}
int size = result.size();
for (int i = 0; i < size; i++) {
std::cout << result[i];
if (i != size - 1) {
std::cout << "-->";
} else {
std::cout << std::endl;
}
}
}
void destroyGraph(GraphNode *root) {
if (!root) {
return;
}
for (auto &neighbor : root->neighbors) {
destroyGraph(neighbor);
}
delete root;
root = nullptr;
}
int main() {
std::string gstr("A(B(D,E),C(F))");
auto *graph = initGraph(gstr);
printf("graph = %p\n", graph);
auto bfs_result = bfs(graph);
printf("graph = %p\n", graph);
auto dfs_result = dfs(graph);
printResult(bfs_result, "BFS: ");
printResult(dfs_result, "DFS: ");
destroyGraph(graph);
return 0;
};