news 2026/4/18 9:42:56

C++实战:用邻接表实现图的深度优先遍历(附完整代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++实战:用邻接表实现图的深度优先遍历(附完整代码)

C++实战:用邻接表实现图的深度优先遍历(附完整代码)

当你第一次接触图论算法时,可能会被各种抽象概念弄得晕头转向。但作为C++开发者,没有什么比直接动手实现一个算法更能加深理解的了。今天我们就来彻底搞懂如何用邻接表表示图结构,并实现深度优先遍历(DFS)算法——这个在路径查找、拓扑排序等领域广泛应用的基础算法。

邻接表相比邻接矩阵,在稀疏图中能显著节省内存空间。想象一下社交网络中的好友关系:每个人(顶点)的好友列表(邻接表)通常不会太多,这时用邻接表就再合适不过了。下面我们将从零开始,一步步构建完整的解决方案。

1. 图的邻接表表示

在C++中,我们通常用vector容器数组来实现邻接表。这种结构既保留了动态扩展的灵活性,又能通过索引快速访问任意顶点的邻居。

#include <vector> using namespace std; const int MAX_VERTICES = 1000; // 预设最大顶点数 vector<int> adjList[MAX_VERTICES]; // 邻接表数组

邻接表的核心思想很简单:数组的每个元素对应图的一个顶点,存储该顶点的所有邻接顶点。例如adjList[0]包含顶点0的所有邻居。

构建邻接表的典型流程:

  1. 读取顶点数V和边数E
  2. 逐条读取边信息,填充邻接表
  3. (可选)对每个顶点的邻居列表排序,确保遍历顺序一致
int V, E; cin >> V >> E; for(int i = 0; i < E; ++i) { int u, v; cin >> u >> v; adjList[u].push_back(v); // 如果是无向图,还需要添加反向边 // adjList[v].push_back(u); } // 对邻接表排序以确保遍历顺序一致 for(int i = 0; i < V; ++i) { sort(adjList[i].begin(), adjList[i].end()); }

2. 深度优先遍历原理与实现

深度优先遍历就像走迷宫时始终坚持"右手法则"——遇到岔路就选择一条路走到底,直到无路可走再回溯。这种策略天然适合用递归实现。

DFS算法的三个关键点:

  • 访问标记数组(避免重复访问)
  • 递归探索未访问的邻居
  • 回溯机制
bool visited[MAX_VERTICES] = {false}; // 访问标记数组 void dfs(int current) { visited[current] = true; cout << current << " "; // 处理当前顶点(这里简单打印) for(int neighbor : adjList[current]) { if(!visited[neighbor]) { dfs(neighbor); // 递归访问未探索的邻居 } } }

注意:对于非连通图,需要从每个未访问的顶点启动DFS,确保遍历整个图。

3. 完整实现与边界处理

一个健壮的DFS实现需要考虑多种边界情况:空图、孤立顶点、自环边等。下面是完整实现:

#include <iostream> #include <vector> #include <algorithm> using namespace std; const int MAX_V = 1000; vector<int> adj[MAX_V]; bool vis[MAX_V] = {false}; void dfs(int u) { vis[u] = true; cout << u << " "; for(int v : adj[u]) { if(!vis[v]) { dfs(v); } } } void dfsAll(int V) { fill(vis, vis + V, false); // 重置访问标记 for(int i = 0; i < V; ++i) { if(!vis[i]) { dfs(i); } } } int main() { int V, E; cin >> V >> E; // 构建邻接表 for(int i = 0; i < E; ++i) { int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); // 无向图需要双向添加 } // 排序确保遍历顺序一致 for(int i = 0; i < V; ++i) { sort(adj[i].begin(), adj[i].end()); } // 执行DFS遍历 dfsAll(V); return 0; }

时间复杂度分析:

  • 邻接表构建:O(V + E)
  • DFS遍历:O(V + E)
  • 空间复杂度:O(V)(主要来自递归栈和访问数组)

4. 实战应用与变体

掌握了基础DFS后,我们可以轻松扩展出许多实用功能:

1. 路径记录

vector<int> path; void dfsWithPath(int u, int target) { path.push_back(u); if(u == target) { // 找到目标,处理路径 for(int node : path) cout << node << " "; return; } for(int v : adj[u]) { if(!vis[v]) { vis[v] = true; dfsWithPath(v, target); vis[v] = false; // 回溯 } } path.pop_back(); // 回溯 }

2. 连通分量计数

int countComponents(int V) { int count = 0; fill(vis, vis + V, false); for(int i = 0; i < V; ++i) { if(!vis[i]) { dfs(i); ++count; } } return count; }

3. 拓扑排序(有向无环图)

vector<int> topoOrder; void topologicalSort(int u) { vis[u] = true; for(int v : adj[u]) { if(!vis[v]) { topologicalSort(v); } } topoOrder.push_back(u); // 后序添加 }

5. 性能优化与常见陷阱

优化技巧:

  • 使用迭代代替递归避免栈溢出
  • 对大型图考虑并行DFS
  • 使用位运算压缩访问标记数组

常见错误:

  1. 忘记重置访问标记数组
  2. 混淆有向图和无向图的边添加方式
  3. 忽略非连通图的情况
  4. 递归深度过大导致栈溢出
// 迭代版DFS示例(使用栈) void dfsIterative(int start) { stack<int> s; s.push(start); vis[start] = true; while(!s.empty()) { int u = s.top(); s.pop(); cout << u << " "; // 注意压栈顺序要与递归顺序一致 for(auto it = adj[u].rbegin(); it != adj[u].rend(); ++it) { int v = *it; if(!vis[v]) { vis[v] = true; s.push(v); } } } }

在实际项目中,我经常遇到需要调整DFS遍历顺序的情况。比如在处理依赖关系时,逆后序的DFS结果直接就是拓扑排序。而调试DFS最有效的方法,就是在关键节点打印状态信息,配合小规模测试用例逐步验证。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 6:49:24

哔哩下载姬完整指南:从零开始掌握B站视频下载技巧

哔哩下载姬完整指南&#xff1a;从零开始掌握B站视频下载技巧 【免费下载链接】downkyi 哔哩下载姬downkyi&#xff0c;哔哩哔哩网站视频下载工具&#xff0c;支持批量下载&#xff0c;支持8K、HDR、杜比视界&#xff0c;提供工具箱&#xff08;音视频提取、去水印等&#xff0…

作者头像 李华
网站建设 2026/4/15 11:42:24

从 GW_CORE 到 SAP_GWFND,读懂 AS ABAP 7.00 至 7.31 时代的 SAP Gateway 组件版图

在维护老的 ECC 或 NetWeaver 7.31 系统时,最容易把人绕进去的地方,往往不是 OData 协议本身,而是系统里那一串彼此相像、职责又并不相同的 Gateway 组件名。你在装包或查依赖时,看到的常常不是今天大家熟悉的 SAP_GWFND,而是 GW_CORE、IW_FND、IW_BEP,某些审批场景里还会…

作者头像 李华
网站建设 2026/4/18 7:19:52

Comsol几何操作实战:从对称分割到三维建模的5个高效技巧

Comsol几何操作实战&#xff1a;从对称分割到三维建模的5个高效技巧 在工程仿真领域&#xff0c;几何建模往往是整个分析流程中最耗时却又至关重要的环节。许多工程师在使用Comsol时&#xff0c;常常陷入重复性操作或低效建模的困境&#xff0c;导致宝贵的时间浪费在基础几何处…

作者头像 李华
网站建设 2026/4/14 11:36:37

AutoCAD字体管理的革命性解决方案:FontCenter免费插件深度解析

AutoCAD字体管理的革命性解决方案&#xff1a;FontCenter免费插件深度解析 【免费下载链接】FontCenter AutoCAD自动管理字体插件 项目地址: https://gitcode.com/gh_mirrors/fo/FontCenter AutoCAD字体缺失问题是每个设计师和工程师都曾面临的痛点&#xff0c;FontCent…

作者头像 李华
网站建设 2026/4/15 15:22:36

【MATLAB代码介绍】使用EKF融合惯导和DVL(速度)的MATLAB仿真例程

基于 MATLAB 编写的扩展卡尔曼滤波&#xff08;EKF&#xff09;仿真程序。该代码旨在模拟和验证 EKF 算法在处理非线性系统时的状态估计性能&#xff0c;具体场景设定为惯性导航系统&#xff08;INS&#xff09;与多普勒测速仪&#xff08;DVL&#xff09;的数据融合。 如有程序…

作者头像 李华
网站建设 2026/4/15 17:30:32

如何一键导出微信聊天记录:3个简单步骤永久保存你的数字记忆

如何一键导出微信聊天记录&#xff1a;3个简单步骤永久保存你的数字记忆 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we…

作者头像 李华