跳到主要内容
深度优先搜索 (DFS) 算法原理及 C++ 实现 | 极客日志
C++ 算法
深度优先搜索 (DFS) 算法原理及 C++ 实现 综述由AI生成 深度优先搜索(DFS)是一种基于栈结构遍历或搜索树或图的算法,核心策略为“不撞南墙不回头”,即沿路径深入探索直至无路可走再回溯。该算法支持递归与迭代两种实现方式,时间复杂度为 O(V+E)。文章解析了 DFS 的核心原理、三色标记法及递归栈机制,提供了 C++ 递归版、迭代版及二叉树遍历的代码示例。同时涵盖连通分量检测、路径总和计算及全排列生成等典型应用场景,并总结了常见误区与优化建议,帮助开发者掌握 DFS 的实际应用。
深海蔚蓝 发布于 2026/3/21 更新于 2026/6/8 18 浏览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 通常用递归实现,天然契合'深入 - 回溯'的过程:
DFS(节点 u):
标记 u 为已访问(白色 → 灰色)
对于 u 的每个邻接节点 v:
如果 v 未被访问:
DFS(v)
标记 u 为完全处理(灰色 → 黑色)
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 : graph[cur]) {
if (!visited[nei]) stk.push (nei);
}
}
}
1.6 关键特性总结 特性 说明 遍历顺序 沿着深度优先,可能先深后广 空间复杂度 O(h),h 为递归深度(树高) 时间复杂度 O(V+E),每个节点和边访问一次 是否保证最短路径 ❌ 不保证 适用场景 连通性检测、回溯问题、拓扑排序、路径存在性
2. 深度优先搜索 (DFS) 动画演示理解
2.1 动画演示理解 使用 HTML 制作了一个简单的动画演示,包含二叉树和有向图的演示。
<style >
* { box-sizing : border-box; font-family : 'Segoe UI' , Roboto, 'Helvetica Neue' , sans-serif; }
body { background : linear-gradient (145deg , #f6f9fc 0% , #e9f1f8 100% ); min-height : 100vh ; margin : 0 ; display : flex; justify-content : center; align-items : center; padding : 20px ; }
.card { max-width : 1000px ; width : 100% ; background : rgba (255 ,255 ,255 ,0.75 ); backdrop-filter : blur (8px ); border-radius : 40px ; box-shadow : 0 20px 40px rgba (0 ,15 ,30 ,0.2 ), 0 4px 12px rgba (0 ,0 ,0 ,0.05 ); padding : 30px 30px 40px ; border : 1px solid rgba (255 ,255 ,255 ,0.6 ); }
h1 { margin : 0 0 8px 0 ; font-weight : 600 ; font-size : 2.2rem ; color : #0a2a44 ; letter-spacing : -0.5px ; display : flex; align-items : center; gap : 12px ; }
h1 small { font-size : 1rem ; font-weight : 400 ; background : #1e3a5f ; color : white; padding : 6px 14px ; border-radius : 40px ; letter-spacing : 0.3px ; }
.sub { color : #2c3e66 ; margin : 0 0 25px 0 ; font-size : 1.05rem ; border-left : 5px solid #3a6ea5 ; padding-left : 18px ; background : rgba (58 , 110 , 165 , 0.04 ); border-radius : 0 20px 20px 0 ; }
.canvas-container { background : #ffffffd9 ; border-radius : 32px ; padding : 20px ; box-shadow : inset 0 2px 6px rgba (0 ,0 ,0 ,0.04 ), 0 12px 24px -8px rgba (0 ,35 ,70 ,0.2 ); border : 1px solid rgba (255 ,255 ,255 ,0.7 ); }
canvas { display : block; width : 100% ; height : auto; background : #ffffff ; border-radius : 24px ; box-shadow : 0 4px 12px rgba (0 ,0 ,0 ,0.05 ); cursor : pointer; transition : box-shadow 0.2s ; }
canvas :active { box-shadow : 0 2px 4px rgba (0 ,0 ,0 ,0.1 ); }
.info-panel { display : flex; flex-wrap : wrap; gap : 25px ; margin : 25px 0 20px ; align-items : stretch; }
.stack-box { flex : 2 ; min-width : 250px ; background : #f0f4fa ; border-radius : 28px ; padding : 18px 22px ; box-shadow : 0 8px 18px -6px rgba (0 ,32 ,64 ,0.12 ); border : 1px solid #ffffff ; }
.visited-box { flex : 3 ; min-width : 280px ; background : #f0f4fa ; border-radius : 28px ; padding : 18px 22px ; box-shadow : 0 8px 18px -6px rgba (0 ,32 ,64 ,0.12 ); border : 1px solid #ffffff ; }
.label { font-size : 0.85rem ; text-transform : uppercase; letter-spacing : 1px ; font-weight : 600 ; color : #1e3a5f ; margin-bottom : 10px ; display : flex; align-items : center; gap : 8px ; }
.label span { background : #1e3a5f ; color : white; padding : 4px 12px ; border-radius : 30px ; font-size : 0.75rem ; }
.stack-content , .visited-content { font-family : 'JetBrains Mono' , 'Fira Code' , monospace; font-size : 1.2rem ; background : #ffffffc0 ; padding : 15px 18px ; border-radius : 20px ; min-height : 60px ; border : 1px solid #cdddec ; color : #0a2a44 ; word-break : break-all; box-shadow : inset 0 1px 4px #00000008 ; }
.action-badge { margin-top : 12px ; font-size : 1rem ; font-weight : 500 ; color : #1e4f7a ; background : #d3e3f5 ; padding : 8px 16px ; border-radius : 30px ; display : inline-block; }
.control-bar { display : flex; flex-wrap : wrap; gap : 12px ; margin : 28px 0 20px ; align-items : center; justify-content : center; }
button , .selector { background : white; border : none; padding : 12px 22px ; border-radius : 60px ; font-weight : 600 ; font-size : 0.95rem ; box-shadow : 0 6px 14px rgba (0 ,35 ,70 ,0.1 ); display : inline-flex; align-items : center; gap : 8px ; transition : all 0.15s ; border : 1px solid rgba (255 ,255 ,255 ,0.8 ); cursor : pointer; color : #1e3a5f ; }
button :hover { background : #e3efff ; transform : translateY (-2px ); box-shadow : 0 12px 20px -8px #1e3a5f40 ; }
button :active { transform : translateY (1px ); box-shadow : 0 2px 6px #0000001a ; }
select { background : white; border : none; padding : 12px 30px 12px 22px ; border-radius : 60px ; font-weight : 500 ; appearance : none; background-image : url ("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231e3a5f' stroke-width='2'><polyline points='6 9 12 15 18 9'/></svg>" ); background-repeat : no-repeat; background-position : right 18px center; background-size : 14px ; box-shadow : 0 6px 14px rgba (0 ,35 ,70 ,0.1 ); }
.speed-slider { display : flex; align-items : center; gap : 10px ; background : #ffffffd0 ; padding : 6px 18px 6px 22px ; border-radius : 60px ; box-shadow : 0 6px 14px rgba (0 ,35 ,70 ,0.1 ); }
.gif-button { background : #1e3a5f ; color : white; border : 1px solid #ffffff60 ; }
.gif-button :hover { background : #2b4b78 ; }
.step-indicator { font-weight : 600 ; background : #cbddec ; padding : 8px 20px ; border-radius : 60px ; font-size : 0.95rem ; }
footer { text-align : center; color : #3a5f7a ; font-size : 0.85rem ; margin-top : 30px ; opacity : 0.8 ; }
</style >
3. 基本演示代码和实用兼容代码
3.1 基本代码演示 void dfs_recursive (const Graph& 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 Graph& 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>
#include <stack>
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(处理非连通图)
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,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