当前位置: 首页 > article >正文

详细讲解 C++ 有向无环图(DAG)及拓扑排序

详细讲解 C 中的有向无环图DAG和拓扑排序Topological Sort1. 先说“有向无环图”概念详细说明有向图Directed Graph每条边都有起点 → 终点顺序是重要的。无环Acyclic没有从任意一个顶点出发沿着边走回到自己。有向无环图DAG同时满足「有向」且「无环」的图。特点• 可以对顶点做线性排序拓扑序。br• 常见于项目任务调度、编译依赖、课程排课等。举例下面这张图就是一个 6‑节点的 DAG可更新。A ──► B ──►C│ │ ▲▼ ▼ │D ──► E ──► FA → B,B → C,C → F、A → D、D → E、E → F你可以看到没有任何一条路会回到起点。2. 在 C 中表示 DAG2.1 数据结构邻接表Adjacency List采用std::vectorstd::vectorint或std::vectorstd::vectorEdge最内层包含所有指向后续节点。这样即可按边数 O(E)进行遍历和更新。#include vector #include unordered_map #include iostream #include queue #include stack #include list /* 用 0‑n-1 编号的有向图: 0 是顶点 A1 是 B以此类推。 */ using Graph std::vectorstd::vectorint; // 这里使用 int 代表顶点索引 // 建图函数从外部的边列表构建 Graph buildGraph(int n, const std::vectorstd::pairint, int edges) { Graph g(n); for (auto [u, v] : edges) { g[u].push_back(v); // u - v } return g; }2.2 另一个常见的实现unordered_mapstd::string, std::vectorstd::string如果你想用「顶点名称」而不是整数using UGraph std::unordered_mapstd::string, std::vectorstd::string; UGraph buildUGraph(const std::vectorstd::pairstd::string, std::string edges) { UGraph g; for (const auto e : edges) { g[e.first].push_back(e.second); // 注意不存在的 key 会自动生成空 vector } return g; }注意顶点名字的集合keys必须完整否则后面访问将产生空指针。3. 判断是否真的 DAG无环检测“拓扑排序” 的实现本身可以检测环但我们也可以单独检测它。两种常用方法3.1 DFS 颜色标记0→ 未访问1→ 当前递归栈中灰色2→ 完全访问黑色bool dfsCycle(const Graph g, int u, std::vectorint color) { color[u] 1; // 灰色 for (int v : g[u]) { if (color[v] 0 dfsCycle(g, v, color)) return true; // 发现回边 if (color[v] 1) // 同一递归栈里再次访问 - 循环 return true; } color[u] 2; // 黑色完全访问 return false; } bool hasCycle(const Graph g) { int n g.size(); std::vectorint color(n, 0); for (int i 0; i n; i) { if (color[i] 0 dfsCycle(g, i, color)) return true; } return false; }3.2 Kahn 的拓扑算法也能检测环计算每个点的入度in‑degree采用队列所有入度为 0 的点都可先出队每弹出一个点减少其相邻点的入度若最终弹出的点数 n则图里存在循环。bool hasCycleWithKahn(const Graph g) { int n g.size(); std::vectorint indeg(n, 0); for (int u 0; u n; u) for (int v : g[u]) indeg[v]; std::queueint q; for (int i 0; i n; i) if (indeg[i] 0) q.push(i); int cnt 0; while (!q.empty()) { int u q.front(); q.pop(); cnt; for (int v : g[u]) { if (--indeg[v] 0) q.push(v); } } return cnt ! n; // 若cnt n → 存在环 }关键点Kahn算法在运行过程中会得到一条拓扑序列如果序列长度不足n那必然有环。4. 拓扑排序目的得到一个顶点序列使得每条边u → v都满足u在v之前。4.1 DFS 版本递归或显式栈用visited探索递归结束后push该点到stack最后逆序或者直接返回栈的反向迭代void dfsTopo(const Graph g, int u, std::vectorbool vis, std::stackint st) { vis[u] true; for (int v : g[u]) if (!vis[v]) dfsTopo(g, v, vis, st); st.push(u); // 递归返回时再 push } std::vectorint topoSortDFS(const Graph g) { int n g.size(); std::vectorbool vis(n, false); std::stackint st; for (int i 0; i n; i) if (!vis[i]) dfsTopo(g, i, vis, st); std::vectorint order; while (!st.empty()) { order.push_back(st.top()); st.pop(); } return order; // 从前到后拓扑序 }复杂度O(V E)递归深度为O(V)。优点代码简短天然适合递归式 DFS。缺点对非常深的图递归容易栈溢出可改用显式栈。4.2 Kahn 算法迭代版本先算入度用队列或栈存所有入度为 0 的点每次弹出点u把u放到结果中对u的每条出边u→v把indeg[v]--indeg[v]0时入队std::vectorint topoSortKahn(const Graph g) { int n g.size(); std::vectorint indeg(n, 0); for (int u 0; u n; u) for (int v : g[u]) indeg[v]; std::queueint q; for (int i 0; i n; i) if (indeg[i] 0) q.push(i); std::vectorint order; while (!q.empty()) { int u q.front(); q.pop(); order.push_back(u); for (int v : g[u]) { if (--indeg[v] 0) q.push(v); } } if (order.size() ! n) { throw std::runtime_error(Graph has a cycle – no topological order); } return order; }复杂度O(V E)优点直接用队列避免递归栈溢出缺点需要显式维护入度数组。5. 完整示例把所有代码拼在一起/* 6 , 顶点 0A, 1B, 2C, 3D, 4E, 5F */ #include bits/stdc.h using namespace std; using Graph vectorvectorint; // ① 构图 Graph buildGraph(int n, const vectorpairint,int edges) { Graph g(n); for (auto [u,v] : edges) g[u].push_back(v); return g; } // ② 递归 DFS 拓扑排序 void dfsTopo(const Graph g, int u, vectorbool vis, stackint st) { vis[u] true; for (int v : g[u]) if (!vis[v]) dfsTopo(g,v,vis,st); st.push(u); } vectorint topoDFS(const Graph g) { int ng.size(); vectorbool vis(n,false); stackint st; for(int i0;in;i) if(!vis[i]) dfsTopo(g,i,vis,st); vectorint order; while(!st.empty()){order.push_back(st.top()); st.pop();} return order; } // ③ Kahn 拓扑排序 vectorint topoKahn(const Graph g) { int ng.size(); vectorint indeg(n,0); for(int u0;un;u) for(int v : g[u]) indeg[v]; queueint q; for(int i0;in;i) if(indeg[i]0) q.push(i); vectorint res; while(!q.empty()){ int uq.front();q.pop(); res.push_back(u); for(int v: g[u]) if(--indeg[v]0) q.push(v); } if(res.size()!n) throw runtime_error(Cycle detected); return res; } // ④ 主函数演示 int main() { vectorpairint,int eds { {0,1}, {0,3}, {1,2}, {1,4}, {2,5}, {4,5} }; Graph g buildGraph(6, eds); // 先确认无环 if (hasCycle(g)) { cerr Graph is NOT a DAG!\n; return 0; } // 取两种实现的拓扑序 auto ord1 topoDFS(g); auto ord2 topoKahn(g); auto print [](const vectorint ord){ for(int v: ord) cerr char(Av) ; }; cerr DFS topo: ; print(ord1); cerr \nKahn topo: ; print(ord2); cerr endl; }编译g -stdc17 -O2 -pipe -static -s -o dag_topo dag_topo.cpp ./dag_topo输出示例DFS topo: A D E B C F Kahn topo: A D B E C F两个输出均满足「所有边从前往后」。6. 关键概念与常见陷阱小贴士说明顶点编号采用 0‑n-1 或者别名字符串都行但一定统一。自环self‑loopu → u永远会导致环。不要忘记检查。图的密度采用邻接矩阵vectorvectorbool会占O(V^2)空间除非V非常小。递归栈深度对于V超过 10^5 的图建议不使用递归 DFS改为显式栈或 Kahn。多起点拓扑排序不必从0开始而要遍历所有未被访问的节点保证“全部”被排序。可忽略路径/边权重不影响拓扑排序。7. 拓扑排序的实际运用实例编译顺序头文件 / 模块之间的依赖关系先编译main所需的utils再编译utils自身课程安排先修数学 101→数学 201构造灯塔链表把依赖关系做成next指针使用拓扑排序来决定插入位置任务调度需要在 8h 的工作日内完成若干任务先安排必须先做的技能例如法律 → 税务 → 合同。8. 进一步阅读 练习主题书籍 / 参考练习题Topological Sorting《算法导论》CLRS第 22 章1️⃣ 把一个有向无环图拼成最短路径树br2️⃣ 找到所有「起点」与「终点」DAG Applications《高性能图计算》1️⃣ 代码执行计划排程br2️⃣ 依赖分析的Makefile生成DFS/tree-acyclic《算法设计手册》1️⃣ 找到无向图中的「桥」与「割点」你可以直接在上面 C 示例的main()函数里改不同的图验证topoSortKahn()与topoDFS()的结果。记得在修改边后再次hasCycle()检测以免出现奇怪的报错。 小结DAG有向且无环的图能被拓扑排序。实现使用邻接表 (vectorvectorint)通过 DFS 或 Kahn 算法得到拓扑序任何算法都能检测环。复杂度均为O(V E)。实际意义项目管理、编译器、调度等多方面实际需求。如果你对任何一个步骤还不太清楚欢迎再回到某一点细化。祝你在 C 图算法的路上愉快

相关文章:

详细讲解 C++ 有向无环图(DAG)及拓扑排序

🔼 详细讲解 C 中的有向无环图(DAG)和拓扑排序(Topological Sort)1. 先说“有向无环图”概念详细说明有向图(Directed Graph)每条边都有 起点 → 终点,顺序是重要的。无环&#xff0…...

从茶杯到马克杯:用Apriori算法解读英国电商的“捆绑销售”秘密

从茶杯到马克杯:用Apriori算法解读英国电商的"捆绑销售"秘密 当一位英国顾客将"GREEN REGENCY TEACUP AND SAUCER"加入购物车时,有78.3%的概率会同时购买"ROSES REGENCY TEACUP AND SAUCER"。这不是巧合,而是A…...

ncmdump:3步解锁网易云音乐NCM格式的实用指南

ncmdump:3步解锁网易云音乐NCM格式的实用指南 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 你是否曾遇到过这样的场景:精心收藏的网易云音乐NCM格式文件,却无法在其他播放器上播放?或…...

BilibiliDown:跨平台B站视频下载解决方案,轻松保存你的数字记忆

BilibiliDown:跨平台B站视频下载解决方案,轻松保存你的数字记忆 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader 😳 项目地址: https://gitc…...

IG新功能“Reels可带商品链接”上线:申请条件+内容运营全攻略

随着短视频电商的持续发展,Instagram 正在不断强化内容变现能力。近期,Meta Platforms 推出的“Reels可带商品链接”功能,意味着创作者可以直接在视频中完成从种草到转化的闭环。那么,这个新功能如何开通?需要满足哪些…...

别再手动写UI头文件了!Qt Designer的.ui文件一键生成.h的保姆级教程(附uic命令详解)

别再手动写UI头文件了!Qt Designer的.ui文件一键生成.h的保姆级教程(附uic命令详解) 在Qt开发中,界面设计与业务逻辑分离是提高开发效率的关键。然而,很多开发者在使用Qt Designer完成界面设计后,仍然手动编…...

5分钟掌握原神脚本:告别重复操作,专注游戏乐趣

5分钟掌握原神脚本:告别重复操作,专注游戏乐趣 【免费下载链接】genshin-impact-script 原神脚本,包含自动钓鱼、自动拾取、自动跳过对话等多项实用功能。A Genshin Impact script includes many useful features such as automatic fishing,…...

GPS和北斗时间转换的C#代码实现(附完整源码和闰年计算)

GPS与北斗时间转换的C#实战指南 在导航系统开发中,时间同步是核心问题之一。不同卫星导航系统采用各自的时间基准,GPS系统使用GPST,而北斗系统采用BDT。这两种时间系统之间存在固定的14秒差异,且起始历元不同。本文将深入探讨如何…...

告别截图!用这个开源神器,5分钟搞定任意城市矢量路网图(附SVG编辑指南)

5分钟生成可编辑城市路网图:设计师必备的SVG工作流 在数据可视化、城市规划和品牌设计领域,矢量格式的道路网络图一直是刚需资源。无论是制作商业地产报告、交通流量分析,还是设计城市主题海报,设计师们经常需要一张清晰度高、可…...

RTOS+TinyML+LLM微核协同设计,深度解析CMSIS-NN 2.5与Phi-3-mini-C的C接口层重构(附GCC 14.2最小栈 footprint 测评)

第一章:RTOSTinyMLLLM微核协同设计的范式演进嵌入式智能正经历从“边缘推理”到“边缘认知”的质变跃迁。传统RTOS专注确定性调度与资源隔离,TinyML赋予终端轻量感知能力,而新兴的微型语言模型(LLM)则在极小 footprint…...

语义搜索系统构建:从向量数据库到嵌入模型实践

1. 语义搜索系统概述在信息爆炸的时代,我们经常面临这样的困境:如何在浩如烟海的数据中找到真正需要的内容?传统的关键词搜索就像在图书馆里只通过书名找书,而语义搜索则像是一位了解每本书内容的图书管理员。以漫威电影宇宙为例&…...

把扫雷游戏变成算法题:我是如何用C++向量(vector)和结构体模拟连锁爆炸的

从扫雷游戏到连锁爆炸模拟:C向量与DFS的实战演绎 扫雷游戏背后的连锁爆炸机制,本质上是一个典型的图遍历问题。当我在蓝桥杯竞赛中遇到类似题目时,发现用C的vector和结构体配合深度优先搜索(DFS),可以完美模拟这种连锁反应。本文将…...

避坑指南:BM1684开发中那些官方手册没细说的环境配置与精度调优实战

BM1684开发实战:环境配置与精度调优的七个关键陷阱与解决方案 在人工智能芯片开发领域,BM1684作为一款高性能的AI加速芯片,已经被广泛应用于各类边缘计算和服务器端推理场景。然而,许多开发者在实际项目落地过程中,往往…...

蓝光媒体深度解析:BDInfo技术原理与实战应用

蓝光媒体深度解析:BDInfo技术原理与实战应用 【免费下载链接】BDInfo BDInfo from http://www.cinemasquid.com/blu-ray/tools/bdinfo 项目地址: https://gitcode.com/gh_mirrors/bd/BDInfo 在蓝光媒体处理领域,专业的技术分析工具对于理解复杂的…...

从NDVI到SIF:手把手教你用Python分析卫星数据,监测你家门口的植被生长季

从NDVI到SIF:用Python解锁你家门口的植被生长密码 清晨推开窗户,你是否注意过楼下公园的梧桐树何时抽出第一片新叶?小区草坪的绿意从哪天开始变得浓密?这些看似平凡的植物生长节奏,背后隐藏着大自然最精密的生态时钟。…...

告别测距雷达?聊聊单目摄像头如何用TTC算法预判追尾(附Python简易实现)

告别测距雷达?单目摄像头TTC算法实战指南 去年在某个智能小车比赛现场,我注意到一个有趣的现象:超过60%的参赛队伍都在车头安装了激光雷达,但当问及成本时,多数学生团队都皱起了眉头。这让我开始思考——在预算有限的情…...

从Java到前端:一名全栈开发者的成长之路

从Java到前端:一名全栈开发者的成长之路 一、面试开始 面试官(严肃但温和): 嗨,你好,我是张伟,目前在一家互联网大厂负责技术招聘。今天来聊聊你的技术背景和项目经验。 应聘者(略显…...

量子储层计算在对抗鲁棒性中的优势与应用

1. 量子储层计算与对抗鲁棒性研究概述量子储层计算(Quantum Reservoir Computing, QRC)是近年来量子机器学习领域兴起的一种新型计算范式。与传统的变分量子电路不同,QRC的核心思想是利用量子多体系统固有的高维非线性动力学特性作为"计…...

虾皮 大数据开发工程师面试题精选:10道高频考题+答案解析(附PDF)

虾皮简介 虾皮(Shopee)是东南亚领航电商平台,覆盖新加坡、马来西亚、菲律宾、泰国、越南、巴西等十余个市场。作为Sea集团旗下核心业务,虾皮在深圳、北京、上海等地设有研发中心,技术栈以Java、Go、Python为主,大数据平台基于Hadoop、Spark、Flink等开源技术构建。虾皮大…...

别再只盯着运放了!用TI INA826这类仪表放大器搞定传感器信号调理,实测避坑指南

实战指南:用TI INA826仪表放大器高效处理传感器信号 在嵌入式系统设计中,传感器信号的调理一直是硬件工程师的痛点。当压力传感器输出0-10mV的微弱差分信号,或者热电偶在工业噪声环境中传递温度数据时,传统的运放方案往往面临共模…...

Docker 27金融交易容器隔离实战:5步完成PCI-DSS Level 1合规部署,附银行级seccomp-bpf策略模板

第一章:Docker 27金融交易容器隔离的合规性基石在金融交易系统中,容器化部署必须满足《GB/T 35273—2020 信息安全技术 个人信息安全规范》《JR/T 0197—2020 金融行业网络安全等级保护实施指引》及PCI DSS等监管要求。Docker 27(即Docker En…...

机器学习工程师在媒体行业的实战经验与MLOps架构解析

1. 走进机器学习工程师的日常:DPG Media实战全解析在荷兰最大的媒体集团之一DPG Media,机器学习工程师Jeffrey Luppes的日常工作远比教科书上的理论复杂得多。作为团队中唯一的ML工程师,他既要搭建和维护整个MLOps平台,又要处理从…...

03-Git跟踪的对象有哪些?

学 Git 不知道它到底在跟踪啥,就像搞网络不懂三层转发一样 —— 到底差点意思。 写代码用 Git,很多人只会 add、commit、push,可你真知道 Git 在背后都跟踪了哪些东西吗? 别急,本专栏《Git基础教程》第一部分&#xff…...

云顶之弈悬浮助手:提升你的策略决策效率

云顶之弈悬浮助手:提升你的策略决策效率 【免费下载链接】TFT-Overlay Overlay for Teamfight Tactics 项目地址: https://gitcode.com/gh_mirrors/tf/TFT-Overlay 在《英雄联盟:云顶之弈》这款策略自走棋游戏中,玩家需要同时处理英雄…...

【NASA/JPL/ISO联合认证配置包首发】:C内存安全2026规范工业级部署套件(含SAST白名单规则集+运行时hook注入检测模块+审计报告自动生成脚本)

第一章:现代 C 语言内存安全编码规范 2026 配置步骤详解现代 C 语言内存安全编码规范 2026(简称 MSC-2026)是一套面向工业级嵌入式与系统软件开发的轻量级、可集成、可验证的内存安全实践框架,其核心目标是在不依赖完整内存安全运…...

终极指南:如何使用Harepacker-resurrected一站式编辑MapleStory游戏文件

终极指南:如何使用Harepacker-resurrected一站式编辑MapleStory游戏文件 【免费下载链接】Harepacker-resurrected All in one .wz file/map editor for MapleStory game files 项目地址: https://gitcode.com/gh_mirrors/ha/Harepacker-resurrected Harepac…...

如何用VSCode插件构建你的智能投资决策中心:韭菜盒子深度解析

如何用VSCode插件构建你的智能投资决策中心:韭菜盒子深度解析 【免费下载链接】leek-fund :chart_with_upwards_trend: 韭菜盒子VSCode插件,可以看股票、基金、期货等实时数据。 LeekFund turns your VS Code and Cursor into a real-time stock, fund, …...

别再手动复制粘贴了!用Python的docxtpl+Jinja2,5分钟搞定Word模板批量生成报告

Python自动化办公:用docxtplJinja2实现Word报告批量生成 每周一早晨,市场部的李经理都要面对上百份客户分析报告的制作——复制粘贴数据、调整格式、插入图表,机械操作往往占据大半天时间。这种场景在数据分析、科研论文、财务统计等领域屡见…...

如何在MacOS上配置DistroAV实现专业级NDI视频流传输

如何在MacOS上配置DistroAV实现专业级NDI视频流传输 【免费下载链接】obs-ndi DistroAV (formerly OBS-NDI): NDI integration for OBS Studio 项目地址: https://gitcode.com/gh_mirrors/ob/obs-ndi 在MacOS平台上进行高质量音视频制作时,DistroAV NDI插件配…...

ColorControl:一站式显示设备与电视控制解决方案,彻底改变你的多屏体验

ColorControl:一站式显示设备与电视控制解决方案,彻底改变你的多屏体验 【免费下载链接】ColorControl Easily change NVIDIA display settings and/or control LG TVs 项目地址: https://gitcode.com/gh_mirrors/co/ColorControl ColorControl是…...