news 2026/4/18 7:38:41

数据结构:有向无环图

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据结构:有向无环图

有向无环图

资料:https://pan.quark.cn/s/43d906ddfa1bhttps://pan.quark.cn/s/90ad8fba8347https://pan.quark.cn/s/d9d72152d3cf

一、有向无环图的定义

有向无环图(Directed Acyclic Graph,简称DAG)是一类特殊的有向图,其核心特征是不存在任何有向环,即从任意顶点出发,沿着有向边遍历,无法回到该顶点。

有向无环图可形式化表示为G=(V,E),其中V为顶点集合,E为有向边集合,且图中不存在满足v₀=vₖ且包含至少一条边的有向路径v₀→v₁→…→vₖ

二、有向无环图的核心概念

1. 顶点的入度与出度

  • 入度:以该顶点为终点的有向边数量,入度为 0 的顶点称为源点
  • 出度:以该顶点为起点的有向边数量,出度为 0 的顶点称为汇点
  • 一个DAG至少存在一个源点和一个汇点(可通过拓扑排序证明)。

2. 拓扑序列

对DAG顶点的一种线性排序,满足:若图中存在有向边<u,v>,则在序列中u一定位于v之前。

  • 一个DAG的拓扑序列不唯一,例如课程依赖图可能有多种合法的学习顺序。

3. 关键路径

在带权DAG中,从源点到汇点的最长路径称为关键路径,路径上的顶点和边为关键任务,决定了整个工程的最短完成时间。

4. 可达性

对于DAG中的两个顶点uv,若存在从uv的有向路径,则称v可由u到达,DAG的可达性可通过拓扑排序或DFS高效判断。

三、有向无环图的存储方式

DAG的存储方式与普通有向图一致,常用以下两种:

1. 邻接矩阵

n×n二维数组存储,adj[i][j]表示是否存在从顶点ij的有向边,无环特性不影响存储结构,仅需保证矩阵对应的图无环。

  • 优点:查询边是否存在的时间复杂度为O(1)
  • 缺点:空间复杂度为O(n²),适合顶点数较少的DAG。

2. 邻接表

为每个顶点维护一个邻接顶点列表(带权DAG则存储(邻接顶点,权重)二元组),同时可维护入度数组,用于拓扑排序。

  • 优点:空间复杂度为O(|V|+|E|),适合稀疏DAG;
  • 缺点:查询边是否存在需遍历邻接表,时间复杂度为O(outdeg(u))

四、有向无环图的核心算法

1. 拓扑排序

拓扑排序是DAG的标志性算法,有两种经典实现方式:

(1)Kahn算法(入度贪心算法)
  • 核心思想:优先选择入度为 0 的顶点加入拓扑序列,再删除该顶点的所有出边,更新邻接顶点的入度,重复此过程直到所有顶点处理完毕。
  • 步骤
    1. 初始化入度数组,统计每个顶点的入度;
    2. 将所有入度为 0 的顶点加入队列;
    3. 依次取出队列顶点,加入拓扑序列,并将其邻接顶点的入度减 1,若邻接顶点入度变为 0 则加入队列;
    4. 若最终拓扑序列长度等于顶点数,则为合法DAG,否则存在环。
  • 时间复杂度:O(|V|+|E|),可同时检测图是否为DAG。
(2)DFS逆序法
  • 核心思想:通过DFS遍历顶点,当顶点的所有后代顶点都处理完毕后,将其加入栈,最终栈的逆序即为拓扑序列。
  • 步骤
    1. 初始化访问标记数组,遍历所有未访问顶点;
    2. 对每个顶点执行DFS,递归访问其邻接顶点;
    3. 递归回溯时将当前顶点压入栈;
    4. 遍历完成后,依次弹出栈中元素得到拓扑序列。
  • 时间复杂度:O(|V|+|E|),适合需要同时处理连通分量的场景。

2. 关键路径算法

  • 核心步骤
    1. 对DAG进行拓扑排序,得到拓扑序列;
    2. 按拓扑序计算每个顶点的最早开始时间ve):ve[v] = max(ve[u] + weight(u,v)),其中uv的前驱顶点;
    3. 逆拓扑序计算每个顶点的最晚开始时间vl):vl[u] = min(vl[v] - weight(u,v)),其中vu的后继顶点;
    4. 若顶点的ve等于vl,则该顶点在关键路径上,对应的边为关键边。

3. 最长路径求解

在DAG中可通过拓扑排序高效求解单源最长路径:

  1. 对DAG进行拓扑排序;
  2. 按拓扑序松弛顶点的出边,更新后继顶点的最长距离,时间复杂度为O(|V|+|E|)

五、有向无环图的实现示例

1. 邻接表实现(含拓扑排序与关键路径)

fromcollectionsimportdequeclassDAG:def__init__(self,num_vertices):self.num_vertices=num_vertices self.adj_list=[[]for_inrange(num_vertices)]# (v, weight)self.indegree=[0]*num_vertices# 入度数组defadd_edge(self,u,v,weight=1):"""添加有向边<u,v>,默认权重为1"""self.adj_list[u].append((v,weight))self.indegree[v]+=1deftopological_sort_kahn(self):"""Kahn算法实现拓扑排序,返回拓扑序列"""queue=deque()# 初始化入度为0的顶点foriinrange(self.num_vertices):ifself.indegree[i]==0:queue.append(i)topo_order=[]whilequeue:u=queue.popleft()topo_order.append(u)# 遍历u的出边,更新邻接顶点入度forv,_inself.adj_list[u]:self.indegree[v]-=1ifself.indegree[v]==0:queue.append(v)iflen(topo_order)!=self.num_vertices:raiseValueError("图中存在环,不是有向无环图")returntopo_orderdefcritical_path(self):"""求解关键路径,返回关键顶点列表"""try:topo_order=self.topological_sort_kahn()exceptValueErrorase:returnstr(e)# 1. 计算最早开始时间veve=[0]*self.num_verticesforuintopo_order:forv,winself.adj_list[u]:ifve[v]<ve[u]+w:ve[v]=ve[u]+w# 2. 计算最晚开始时间vlvl=[ve[-1]]*self.num_vertices# 汇点的vl等于其veforuinreversed(topo_order):forv,winself.adj_list[u]:ifvl[u]>vl[v]-w:vl[u]=vl[v]-w# 3. 筛选关键顶点(ve[u] == vl[u])critical_vertices=[uforuinrange(self.num_vertices)ifve[u]==vl[u]]returncritical_vertices,ve[-1]# 返回关键顶点和工程最短完成时间

使用示例

# 初始化6个顶点的DAG(模拟项目任务依赖)dag=DAG(6)# 添加带权边:u->v,权重为任务耗时dag.add_edge(0,1,3)dag.add_edge(0,2,2)dag.add_edge(1,3,2)dag.add_edge(2,3,4)dag.add_edge(3,4,3)dag.add_edge(3,5,2)dag.add_edge(4,5,1)# 拓扑排序topo=dag.topological_sort_kahn()print("拓扑序列:",topo)# 输出如[0,2,1,3,4,5]# 关键路径critical_vertices,min_time=dag.critical_path()print("关键顶点:",critical_vertices)# 输出关键顶点列表print("工程最短完成时间:",min_time)# 输出总耗时

六、有向无环图的典型应用

  1. 任务调度:如软件开发中的模块编译顺序、项目管理中的任务执行流程,通过拓扑排序确定合法执行顺序;
  2. 课程安排:大学课程的先修依赖关系,拓扑序列为合法的选课顺序;
  3. 依赖解析:包管理工具(如Maven、npm)的依赖关系分析,避免循环依赖并确定安装顺序;
  4. 项目进度规划:通过关键路径算法确定项目的关键任务和最短完成时间;
  5. 编译优化:编译器中表达式的求值顺序、代码的指令重排,利用DAG消除公共子表达式。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/11 19:17:21

13、Linux 文件归档、压缩与查找实用指南(上)

Linux 文件归档、压缩与查找实用指南(上) 在当今数字化时代,我们面临着海量文件的管理挑战,无论是归档压缩以节省空间,还是快速准确地查找所需文件。Linux 提供了一系列强大的工具来应对这些问题,下面将为你详细介绍相关的操作和技巧。 1. 使用 tar 和 gzip 进行文件归…

作者头像 李华
网站建设 2026/4/17 18:44:59

微信小程序任务管理终极指南:5分钟打造高效待办系统

微信小程序任务管理终极指南&#xff1a;5分钟打造高效待办系统 【免费下载链接】weapp-todos 一个简单的任务清单小程序, awesome weapp demo, todos, todolist 项目地址: https://gitcode.com/gh_mirrors/we/weapp-todos 在快节奏的现代生活中&#xff0c;高效的任务管…

作者头像 李华
网站建设 2026/4/16 20:21:42

Captura视频录制与防抖功能配置完全指南

Captura视频录制与防抖功能配置完全指南 【免费下载链接】Captura Capture Screen, Audio, Cursor, Mouse Clicks and Keystrokes 项目地址: https://gitcode.com/gh_mirrors/ca/Captura 屏幕录制是现代工作学习中不可或缺的工具&#xff0c;但你是否遇到过录制视频时画…

作者头像 李华
网站建设 2026/3/14 23:59:53

Llama-Factory是否支持模型剪枝?轻量化部署方案

Llama-Factory是否支持模型剪枝&#xff1f;轻量化部署方案 在大语言模型&#xff08;LLM&#xff09;快速演进的今天&#xff0c;越来越多的企业和开发者希望将强大的生成能力落地到实际业务中。然而&#xff0c;动辄数十GB显存需求、上百亿参数规模的模型让许多团队望而却步—…

作者头像 李华
网站建设 2026/4/11 9:19:35

56、Linux网络服务配置与安全指南

Linux网络服务配置与安全指南 1. 探索练习 在网络配置和管理中,我们常常会遇到各种实际问题,以下是一些探索性的练习,帮助我们更好地理解和应用网络知识。 1. 子网划分问题 :假设公司使用A类网络100.0.0.0/8,若要将此网络划分为11个子网以匹配11个部门,需要为网络上…

作者头像 李华
网站建设 2026/4/18 5:25:21

Redis 的内存回收机制详解

Redis 的内存回收机制详解 Redis 是基于内存的高性能数据库&#xff0c;但内存资源有限。当单节点内存过大时&#xff0c;不仅会增加成本&#xff0c;还可能影响 RDB/AOF 持久化 和 主从同步 的性能与稳定性。 为此&#xff0c;Redis 提供了完善的内存管理机制&#xff0c;主…

作者头像 李华