跳到主要内容 深度优先搜索 (DFS) 算法原理与 C++ 实现 | 极客日志
C++ 算法
深度优先搜索 (DFS) 算法原理与 C++ 实现 深度优先搜索 (DFS) 算法的核心原理,包括“不撞南墙不回头”的思想、三色标记法及递归与栈的关系。提供了 C++ 递归与迭代版本代码,并展示了连通分量检测、二叉树路径总和及全排列生成等实用场景。内容涵盖算法步骤、复杂度分析及注意事项,帮助读者掌握 DFS 在图论与回溯问题中的应用。
不知所云 发布于 2026/3/23 更新于 2026/4/16 20K 浏览1. 深度优先搜索 (DFS) 核心原理
1.1 核心思想:一条路走到黑
DFS 的核心思想可以用一句俗语概括:'不撞南墙不回头' 。它从起点出发,沿着一条路径尽可能深地探索,直到无法继续(没有未访问的邻接点或到达目标),然后回溯 到上一个分支点,选择另一条未走过的路径继续探索,直到遍历完所有可达节点。
形象比喻:你在迷宫中,随便选一个方向,一直往前走,遇到死胡同就退回到上一个岔路口,换一个方向继续走。这个过程就是 DFS。
1.2 工作过程详解
我们通过一个简单的树形结构来可视化 DFS 的工作过程(节点访问顺序用数字标记):
A (1 )
/ \
B (2 ) E (5 )
/ \ \
C (3 ) D (4 ) F (6 )
从 A 出发的 DFS 遍历顺序为:A → B → C → D → E → F
具体步骤:
访问 A,标记已访问 。
从 A 的第一个邻接点 B 出发,访问 B,标记 。
从 B 的第一个邻接点 C 出发,访问 C,标记 。
C 无未访问邻接点,回溯 到 B。
从 B 的下一个邻接点 D 出发,访问 D,标记 。
D 无未访问邻接点,回溯 到 B;B 也无其他邻接点,回溯 到 A。
从 A 的下一个邻接点 E 出发,访问 E,标记 。
从 E 的第一个邻接点 F 出发,访问 F,标记 。
F 无未访问邻接点,回溯 到 E;E 无其他邻接点,回溯 到 A。
A 所有邻接点访问完毕,结束。
1.3 三色标记法
在 DFS 执行过程中,常用三种颜色标记节点状态,这有助于理解递归栈和回溯:
白色 :尚未发现的节点(未被访问)。
灰色 :已发现但尚未处理完所有邻接点的节点(当前在递归栈中)。
黑色 :所有邻接点都已处理完毕的节点(已完成探索)。
这个标记法在环检测、拓扑排序等高级应用中至关重要。
1.4 算法步骤(递归版本)
DFS 通常用递归实现,天然契合'深入 - 回溯'的过程:
void DFS (int u) {
visited[u] = ;
( v : adj[u]) {
(!visited[v]) {
(v);
}
}
}
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
true
for
int
if
DFS
1.5 递归与栈的关系 递归的本质是系统栈。每次递归调用都会将当前状态(节点、局部变量)压入系统栈,回溯时从栈顶弹出。因此,DFS 也可以显式使用栈 来实现非递归版本,避免递归深度限制。
stack<int > stk;
stk.push (start);
while (!stk.empty ()) {
int cur = stk.top ();
stk.pop ();
if (!visited[cur]) {
visited[cur] = true ;
for (int nei : adj[cur]) {
if (!visited[nei]) {
stk.push (nei);
}
}
}
}
1.6 关键特性总结 特性 说明 遍历顺序 沿着深度优先,可能先深后广 空间复杂度 O(h),h 为递归深度(树高) 时间复杂度 O(V+E),每个节点和边访问一次 是否保证最短路径 ❌ 不保证 适用场景 连通性检测、回溯问题、拓扑排序、路径存在性
2. 深度优先搜索 (DFS) 动画演示理解
2.1 动画演示理解 通过动态演示可以更直观地理解 DFS 的遍历过程,包含二叉树和有向图的示例。动图展示了节点被访问的顺序以及栈的变化情况。
3. 基本演示代码和实用兼容代码
3.1 基本代码演示 void dfs_recursive (const vector<vector<int >>& graph, int node, unordered_set<int >& visited) {
visited.insert (node);
cout << node << " " ;
for (int neighbor : graph[node]) {
if (visited.find (neighbor) == visited.end ()) {
dfs_recursive (graph, neighbor, visited);
}
}
}
void dfs_iterative (const vector<vector<int >>& graph, int start) {
unordered_set<int > visited;
stack<int > stk;
stk.push (start);
while (!stk.empty ()) {
int node = stk.top ();
stk.pop ();
if (visited.find (node) == visited.end ()) {
visited.insert (node);
cout << node << " " ;
const auto & neighbors = graph[node];
for (auto it = neighbors.rbegin (); it != neighbors.rend (); ++it) {
if (visited.find (*it) == visited.end ()) {
stk.push (*it);
}
}
}
}
}
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode (int x) : val (x), left (nullptr ), right (nullptr ) {}
};
void dfs_tree (TreeNode* root) {
if (!root) return ;
cout << root->val << " " ;
dfs_tree (root->left);
dfs_tree (root->right);
}
3.2 实用兼容代码
1. 连通分量检测(无向图) 应用场景 :社交网络中的朋友圈划分、图像连通域标记等。
#include <iostream>
#include <vector>
using namespace std;
void dfs (int node, vector<vector<int >>& graph, vector<bool >& visited, vector<int >& component) {
visited[node] = true ;
component.push_back (node);
for (int neighbor : graph[node]) {
if (!visited[neighbor]) dfs (neighbor, graph, visited, component);
}
}
vector<vector<int >> findConnectedComponents (vector<vector<int >>& graph, int n) {
vector<bool > visited (n, false ) ;
vector<vector<int >> components;
for (int i = 0 ; i < n; ++i) {
if (!visited[i]) {
vector<int > comp;
dfs (i, graph, visited, comp);
components.push_back (comp);
}
}
return components;
}
int main () {
vector<vector<int >> graph = {{1 }, {0 ,2 }, {1 }, {4 }, {3 }, {}};
int n = 6 ;
auto comps = findConnectedComponents (graph, n);
cout << "连通分量:" << endl;
for (auto & comp : comps) {
for (int x : comp) cout << x << " " ;
cout << endl;
}
return 0 ;
}
2. 二叉树路径总和 II(返回所有满足条件的路径) 应用场景 :路径规划、决策树回溯(如求所有和为某值的路径)。
#include <iostream>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode (int x) : val (x), left (nullptr ), right (nullptr ) {}
TreeNode (int x, TreeNode* l, TreeNode* r) : val (x), left (l), right (r) {}
};
void dfs (TreeNode* node, int currentSum, int targetSum, vector<int >& path, vector<vector<int >>& res) {
if (!node) return ;
currentSum += node->val;
path.push_back (node->val);
if (!node->left && !node->right && currentSum == targetSum) {
res.push_back (path);
} else {
dfs (node->left, currentSum, targetSum, path, res);
dfs (node->right, currentSum, targetSum, path, res);
}
path.pop_back ();
}
vector<vector<int >> pathSum (TreeNode* root, int targetSum) {
vector<vector<int >> res;
vector<int > path;
dfs (root, 0 , targetSum, path, res);
return res;
}
int main () {
TreeNode* root = new TreeNode (5 );
root->left = new TreeNode (4 , new TreeNode (11 , new TreeNode (7 ), new TreeNode (2 )), nullptr );
root->right = new TreeNode (8 , new TreeNode (13 ), new TreeNode (4 , nullptr , new TreeNode (1 )));
int target = 22 ;
auto paths = pathSum (root, target);
cout << "路径总和为 " << target << " 的路径:" << endl;
for (auto & p : paths) {
for (int v : p) cout << v << " " ;
cout << endl;
}
return 0 ;
}
3. 全排列生成(回溯法) 应用场景 :密码破解、排列组合枚举、旅行商问题初始排列。
#include <iostream>
#include <vector>
using namespace std;
void dfs (vector<int >& nums, vector<bool >& used, vector<int >& path, vector<vector<int >>& res) {
if (path.size () == nums.size ()) {
res.push_back (path);
return ;
}
for (int i = 0 ; i < nums.size (); ++i) {
if (!used[i]) {
used[i] = true ;
path.push_back (nums[i]);
dfs (nums, used, path, res);
path.pop_back ();
used[i] = false ;
}
}
}
vector<vector<int >> permute (vector<int >& nums) {
vector<vector<int >> res;
vector<int > path;
vector<bool > used (nums.size(), false ) ;
dfs (nums, used, path, res);
return res;
}
int main () {
vector<int > nums = {1 ,2 ,3 };
auto perms = permute (nums);
cout << "全排列:" << endl;
for (auto & p : perms) {
for (int v : p) cout << v << " " ;
cout << endl;
}
return 0 ;
}
4. 注意事项 误区 正确做法 忘记标记 visited 进入 DFS 前立即标记,防止重复访问 回溯时忘记撤销 如果在递归前修改了状态(如 path),递归后一定要还原 递归深度过大导致栈溢出 改用迭代栈,或设置递归深度限制 在无向图中遍历所有节点 主函数要遍历每个节点,对未访问的调用 DFS(处理非连通图)