拓扑排序和关键路径详解

2024-06-14 07:52

本文主要是介绍拓扑排序和关键路径详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

拓扑排序

关键路径

拓扑排序

如果有一个有向图的任意顶点都无法通过一些有向边回到身边,那么称这个有向图为有向无环图。

拓扑排序是将有向无环图的所有顶点排成一个线性序列,使得对图中的任意两个顶点u,v,如果存在边u->v,那么在序列中u一定在v前面,这个序列被称为拓扑序列。

拓扑排序实现的步骤如下:

(1)定义一个队列Q,并将所以入度为0的结点加入队列。

(2)取队首元素,输出。然后删去所有从它出发的边,并令这些边到达的顶点的入度减1,如果某个顶点的入度减为0,则将其加入队列。

(3)反复进行(2)操作,直到队列为空。如果队列为空时入过队的结点数目恰好为N,说明拓扑排序成功,图为有向无环图,否则,拓扑排序失败,图中有环。

可以用邻接表实现拓扑排序。显然,由于需要记录结点的入度,因此需要额外建立一个数组inDegree[maxn],并在程序一开始读入图时就记录好每个结点的入度。接下来只需要按上面说的步骤实现即可。

vector<int> G[maxn];
int n,m,inDegree[maxn];
bool topologicalSort(){int num=0;queue<int> q;for(int i=0;i<n;i++){if(inDegree[i]==0){q.push(i);}}while(!q.empty()){int u=q.front();q.pop();for(int i=0;i<G[u].size();i++){int v=G[u][i];inDegree[v]--;if(inDegree[i]==0){q.push(v);}}G[u].clear();num++;}if(num==n){return true;}else{return false;}
}

拓扑排序的一个重要的应用就是判断一个给定的图是否是有向无环图。如果有多个入度为0的顶点,选择编号最小的顶点,把么把queue改为priority_queue,并保持队首元素是优先队列的最小的元素即可。

关键路径

AOE网是指用带权的边集表示活动,而用顶点表示时间的有向图。关键路径就是AOE网中的最长路径,而把关键路径上的活动称为关键活动。

(1)计算一个事件的最早发生时间ve数组,可以使用拓扑排序实现,在拓扑排序访问到某个结点时,不是让它去找前驱结点来更新ve[i],而是使用ve[i]去更新其所有后继结点的ve值。

//拓扑排序 ,求ve数组 
stack<int> topOrder;
bool topOrdercalsort(){queue<int> q;for(int i=0;i<n;i++){if(inDegree[i]==0){q.push(i);}}while(!q.empty()){int u=q.front();q.pop();topOrder.push(u);for(int i=0;i<G[u].size();i++){int v=G[u][i].v;inDegree[v]--;if(inDegree[v]==0){q.push(v);}//用ve[u]来更新u的所有后继结点 if(ve[u]+G[u][i].w>ve[v]){ve[v]=ve[u]+G[u][i].w;}}}if(topOrder.size()==n){return true;}else{return false;}
}

(2)计算一个事件的最晚发生时间vl数组,可以通过逆拓扑排序来实现。可以通过颠倒拓扑排序来得到一组合法的逆拓扑排序。此时会发现,在上面实现拓扑排序时使用了栈来存储拓扑序列,那么只需要按顺序出栈就是逆拓扑序列。

fill(vl,vl+n,ve[n-1]);
while(!topOrder.empty()){int u=topOrder.top();topOrder.pop();for(int i=0;i<G[u].size();i++){int v=G[u][i].v;if(vl[v]-G[u][i].w<vl[u]){vl[u]=vl[v]-G[u][i].w;}}
}

(3)先求点,再夹边。下面是主体部分的代码。(适用汇点确定且唯一的情况)

//关键路径,不是有向无环图返回-1,否则返回关键路径长度 
int CriticalPath(){memset(ve,0,sizeof(ve));if(topOrdercalsort()==false){return -1;}fill(vl,vl+n,ve[n-1]);//直接使用topOrder出栈即为逆拓扑序列,求解vl数组 while(!topOrder.empty()){int u=topOrder.top();topOrder.pop();for(int i=0;i<G[u].size();i++){int v=G[u][i].v;if(vl[v]-G[u][i].w<vl[u]){vl[u]=vl[v]-G[u][i].w;}}}//遍历邻接表的所有边,计算活动的最早开始时间e和最迟开始时间lfor(int u=0;u<n;u++){for(int i=0;i<G[u].size();i++){int v=G[u][i].v,w=G[u][i].w;int e=ve[u],l=vl[v]-w;if(e==l){printf("%d->%d\n",u,v);}}} return ve[n-1];
}

在上面的代码中没有将活动的最早开始时间e和最晚开始时间l存储下来,因为一般来说e和l只是用来判断当前活动是否存在关键活动,没有必要单独存下来。如果确实想要存下来,只需要在结构体node中添加域e和l即可。

如果事先不知道汇点编号,最快的办法获得关键路径长度就是取ve数组的最大值。因为ve数组的含义是事件的最早开始时间,因此所有事件中ve最大的一定是最后一个事件,也就是汇点。于是只需要在fill函数之前添加一小段语句,然后改变vl函数初始值即可。

int maxLength=0;
for(int i=0;i<n;i++){if(ve[i]>maxLength){maxLength=ve[i];}
}
fill(vl,vl+n,maxLength);

即便图中有多条关键路径,但是如果题目只要求输出关键活动,按照上面的写法就可以。如果要求输出所有的关键路径,就需要把关键活动存下来,方法就是新建一个邻接表,当确定边u->v是关键活动时,就将它加入邻接表。这样最后生成的邻接表就是所有关键路径合成的图,可以用dfs遍历来获取所有关键路径。

这篇关于拓扑排序和关键路径详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1059813

相关文章

随想录 Day 69 并查集 107. 寻找存在的路径

随想录 Day 69 并查集 107. 寻找存在的路径 理论基础 int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好vector<int> father = vector<int> (n, 0); // C++里的一种数组结构// 并查集初始化void init() {for (int i = 0; i < n; ++i) {father[i] = i;}

十四、观察者模式与访问者模式详解

21.观察者模式 21.1.课程目标 1、 掌握观察者模式和访问者模式的应用场景。 2、 掌握观察者模式在具体业务场景中的应用。 3、 了解访问者模式的双分派。 4、 观察者模式和访问者模式的优、缺点。 21.2.内容定位 1、 有 Swing开发经验的人群更容易理解观察者模式。 2、 访问者模式被称为最复杂的设计模式。 21.3.观察者模式 观 察 者 模 式 ( Obser

【操作系统】信号Signal超详解|捕捉函数

🔥博客主页: 我要成为C++领域大神🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞👍收藏⭐评论✍️ 本博客致力于知识分享,与更多的人进行学习交流 ​ 如何触发信号 信号是Linux下的经典技术,一般操作系统利用信号杀死违规进程,典型进程干预手段,信号除了杀死进程外也可以挂起进程 kill -l 查看系统支持的信号

Jitter Injection详解

一、定义与作用 Jitter Injection,即抖动注入,是一种在通信系统中人为地添加抖动的技术。该技术通过在发送端对数据包进行延迟和抖动调整,以实现对整个通信系统的时延和抖动的控制。其主要作用包括: 改善传输质量:通过调整数据包的时延和抖动,可以有效地降低误码率,提高数据传输的可靠性。均衡网络负载:通过对不同的数据流进行不同程度的抖动注入,可以实现网络资源的合理分配,提高整体传输效率。增

SQL Server中,用Restore DataBase把数据库还原到指定的路径

restore database 数据库名 from disk='备份文件路径' with move '数据库文件名' to '数据库文件放置路径', move '日志文件名' to '日志文件存放置路径' Go 如: restore database EaseWe from disk='H:\EaseWe.bak' with move 'Ease

Steam邮件推送内容有哪些?配置教程详解!

Steam邮件推送功能是否安全?如何个性化邮件推送内容? Steam作为全球最大的数字游戏分发平台之一,不仅提供了海量的游戏资源,还通过邮件推送为用户提供最新的游戏信息、促销活动和个性化推荐。AokSend将详细介绍Steam邮件推送的主要内容。 Steam邮件推送:促销优惠 每当平台举办大型促销活动,如夏季促销、冬季促销、黑色星期五等,用户都会收到邮件通知。这些邮件详细列出了打折游戏、

探索Elastic Search:强大的开源搜索引擎,详解及使用

🎬 鸽芷咕:个人主页  🔥 个人专栏: 《C++干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 引入 全文搜索属于最常见的需求,开源的 Elasticsearch (以下简称 Elastic)是目前全文搜索引擎的首选,相信大家多多少少的都听说过它。它可以快速地储存、搜索和分析海量数据。就连维基百科、Stack Overflow、

数据结构9——排序

一、冒泡排序 冒泡排序(Bubble Sort),顾名思义,就是指越小的元素会经由交换慢慢“浮”到数列的顶端。 算法原理 从左到右,依次比较相邻的元素大小,更大的元素交换到右边;从第一组相邻元素比较到最后一组相邻元素,这一步结束最后一个元素必然是参与比较的元素中最大的元素;按照大的居右原则,重新从左到后比较,前一轮中得到的最后一个元素不参4与比较,得出新一轮的最大元素;按照上述规则,每一轮结

常用MQ消息中间件Kafka、ZeroMQ和RabbitMQ对比及RabbitMQ详解

1、概述   在现代的分布式系统和实时数据处理领域,消息中间件扮演着关键的角色,用于解决应用程序之间的通信和数据传递的挑战。在众多的消息中间件解决方案中,Kafka、ZeroMQ和RabbitMQ 是备受关注和广泛应用的代表性系统。它们各自具有独特的特点和优势,适用于不同的应用场景和需求。   Kafka 是一个高性能、可扩展的分布式消息队列系统,被设计用于处理大规模的数据流和实时数据传输。它

七种排序方式总结

/*2018.01.23*A:YUAN*T:其中排序算法:冒泡排序,简单排序,直接插入排序,希尔排序,堆排序,归并排序,快速排序*/#include <stdio.h>#include <math.h>#include <malloc.h>#define MAXSIZE 10000#define FALSE 0#define TRUE 1typedef struct {i