拓扑排序与关键路径

news2024/11/19 9:20:25

一、拓扑排序

1.1 什么是拓扑排序

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。–《百度百科》

简单来讲,对于下面这张图,要想访问到B点,就必须先访问A点;要想访问E点,就必须先访问B点和C点…拓扑排序就是在保证各节点的优先级顺序不被打乱的前提下,遍历整张图的节点。拓扑排序形成的拓扑序列不一定只有一条。比如下面这张图的拓扑序列可以是:A->B->C->D->E->F->G,也可以是A->D->C->B->F->E->G。如果图的所有节点都遍历到了,那么这就是一张无环图;但如果有节点没有被遍历到,那么这张图一定存在环。

1.2 算法流程

拓扑排序的算法流程很简单:从图中找到一个入度为0的节点输出,然后删除这个顶点(包括依赖它的边)。重复此步骤,直到图中不存在入度为0的节点为止。

以前面的图为例。在起始状态下,图中入度为0的节点只有A,所以删除A节点

此时图中入度为0的节点有B、C、D。选择B点删除

此时入度为0的点还剩下C、D。选择C删除

重复上述过程,直到没有入度为0的节点为止。

代码如下(这里的图采用邻接链表存储)

/// <summary>
/// 拓扑排序
/// </summary>
/// <param name="graph"></param>
private void Topological<T>(GraphByAdjacencyList<T> graph)
{
	Stack<int> stack = new Stack<int>(graph.Count);
	// 将入度为0的点加入栈
	for (int i = 0; i < graph.Count; i++)
	{
		if (graph.Nodes[i].inWeight == 0)
		{
			stack.Push(i);
		}
	}

	while (stack.Count > 0)
	{
		var nodeIndex = stack.Pop();
		Console.Write(graph.Nodes[nodeIndex].data+"->");
		// 遍历邻接链表
		var edge = graph.Nodes[nodeIndex].next;
		while (edge != null)
		{
			// 将入度都-1
			var index = edge.index;
			graph.Nodes[index].inWeight--;
			// 如果有入度为0的顶点,则入栈
			if (graph.Nodes[index].inWeight == 0)
			{
				stack.Push(index);
			}
			edge = edge.next;
		}
	}
}

二、关键路径

2.1 什么是关键路径

对于下面这张有向带权图,假设我们用边表示活动,边的权值表示活动的持续时间,顶点表示事件,则这张图就是一张表示活动的网,我们称之为AOE网。AOE网中没有入边的顶点为始点,没有出边的顶点为终点。

如果这张网表示的是一个工程,那么A点表示的就是工程的开始,G点表示的就是工程的结束。整个工程的耗时肯定不是所有边的权值总和,因为诸如A->B、A->C、A->D这类活动是可以并行进行的,所以整个工程的耗时取决于从始点到终点长度最大的路径。我们把路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫关键活动。

至于如何找到这条关键路径就是接下来要讲的关键路径算法的任务了。

2.2 关键路径算法

我们先从最简单的开始入手,来看下面这张AOE网。

如果要完成这项工程,就需要A->B->CA->C全部完成。对于A->B来说,因为A->C需要4天,所以即便自己晚几天开工也不迟。所以A->B的最早开始时间是0,也就是立即开工。最晚开始时间是3,因为还有给B->C留够时间。对于A->C来说,它的最早开始时间是0,最晚开始时间也是0,因为稍微晚一点开工就会造成工程整体延后。所以A->C就是关键活动。不难看出,对于任何一个活动,如果最早开始时间和最晚开始时间不相同,说明它不是关键活动。如果相同,则是关键活动。关键活动组成的路径就是关键路径。

为了求出活动(边)的最早和最晚开始时间,我们就需要先知道事件(顶点)的最早和最晚发生时间。所以我们先事先定义下面几个变量:

  • 事件的最早发生时间etv(earliest time of vertex)
  • 事件的最晚发生时间ltv(latest time of vertex)
  • 活动的最早开工时间ete(earliest time of edge)
  • 活动的最晚开工时间lte(latest time of edge)

接下来求事件最早发生时间。要求事件的最早发生时间,就需要弄清楚各事件间的依赖关系。比如开头这张图,假设我们要求G点的最早发生时间,就只需要知道E、F的最早发生时间 E t 、 F t E_t、F_t EtFt,然后在 E t 、 F t E_t、F_t EtFt的基础上加上活动的时间,再取它们之间的最大值即可。

按照依赖关系遍历整张图,这正是前面拓扑排序的专长。所以我们只需要对之前拓扑排序的算法进行一点点改造,即可拿到我们想要的结果。

/// <summary>
/// 改进的拓扑排序
/// </summary>
/// <param name="graph"></param>
/// <param name="etv"></param>
private Stack<int> Topological2<T>(GraphByAdjacencyList<T> graph,out int[] etv)
{
	Stack<int> stack = new Stack<int>(graph.Count);
	// 将入度为0的点加入栈
	for (int i = 0; i < graph.Count; i++)
	{
		if (graph.Nodes[i].inWeight == 0)
		{
			stack.Push(i);
		}
	}
	// ..............新增Start.....................
	// 用来存储拓扑排序的结果并返回
	Stack<int> res = new Stack<int>(graph.Count);
	// 事件最早发生时间
	etv = new int[graph.Count];
	// ..............新增End.......................
	while (stack.Count > 0)
	{
		var nodeIndex = stack.Pop();
		// 遍历邻接链表
		var edge = graph.Nodes[nodeIndex].next;
		// ..............新增Start.....................
		// 将拓扑排序结果存入结果栈
		res.Push(nodeIndex);
		// ..............新增End.......................
		while (edge != null)
		{
			// 将入度都-1
			var index = edge.index;
			graph.Nodes[index].inWeight--;
			// 如果有入度为0的顶点,则入栈
			if (graph.Nodes[index].inWeight == 0)
			{
				stack.Push(index);
			}
			// ..............新增Start.....................
			// 如果(上一事件发生时间+活动持续时间)>当前记录的最早发生时间 则更新
			if (etv[nodeIndex] + edge.weight > etv[index])
			{
				etv[index] = etv[nodeIndex] + edge.weight;
			}
			// ..............新增End.......................
			edge = edge.next;
		}
	}
	return res;
}

有了事件最早发生时间,那么最晚发生时间也可以相应的求出来了。还是以这张图为例,假设我们要求的是C点的最晚发生时间,那就只需要先求出E、F的最晚发生时间,然后减去活动时间,取最小值即可。而E、F的最晚发生时间又可以由G点计算出。G点的最晚发生时间与最早发生时间是一致的(因为始点和终点一定在关键路径中),所以理论上这些点的最晚发生时间就都可以计算出来。

现在,图中的所有事件的最早发生时间和最晚发生时间我们都求出来了,接下来就是计算所有活动的最早开工时间和最晚开工时间。还是拿出前面那张图,对于C->E这项活动,它的最早开工时间与C的最早发生时间是一致的(事件刚发生就可以开工)。但它的最晚开工时间则取决于E的最晚发生时间(只要拖到E发生前做完就可以),即E的最晚开工时间 - 活动时间。

理解了这几个变量的计算方式,我们就可以开始写代码了

/// <summary>
/// 关键路径算法
/// </summary>
/// <param name="graph"></param>
private void CriticalPath<T>(GraphByAdjacencyList<T> graph)
{
	// 通过拓扑排序计算事件最早发生时间
	var topoStack = Topological2(graph, out int[] etv);
	// 定义事件最晚发生时间并初始化为终点的最早发生时间
	int[] ltv = new int[graph.Count];
	for (int i = 0; i < graph.Count; i++)
	{
		ltv[i] = etv[graph.Count - 1];
	}
	// 求事件最晚发生时间
	while (topoStack.Count > 0)
	{
		int nodeIndex = topoStack.Pop();
		// 遍历邻接链表
		var edge = graph.Nodes[nodeIndex].next;
		while (edge != null)
		{
			// 如果(下一个事件的最晚发生时间 - 活动时间) < 当前记录的最晚发生时间
			// 则意味着需要把工期提前
			if (ltv[edge.index] - edge.weight < ltv[nodeIndex] )
			{
				ltv[nodeIndex] = ltv[edge.index] - edge.weight;
			}
			edge = edge.next;
		}
	}

	for (int i = 0; i < graph.Count; i++)
	{
		// 遍历所有边
		var edge = graph.Nodes[i].next;
		while (edge != null)
		{
			// 最早开工时间 = 起始事件的最早发生时间
			int ete = etv[i];
			// 最晚开工时间 = 结束事件的最晚发生时间 - 活动时间
			int lte = ltv[edge.index] - edge.weight;
			// 最早开工时间 == 最晚开工时间,说明是关键活动
			if (ete == lte)
			{
				// 打印路径
				Console.Write($" {graph.Nodes[i].data}->{graph.Nodes[edge.index].data} ");
			}
			edge = edge.next;
		}
	}
}

三、参考资料

[1]. 《大话数据结构》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/69876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

入驻淘宝成人用品店铺要什么证件?

在淘宝店铺开一家淘宝店铺&#xff0c;必须要有成人用品特种经营许可证&#xff0c;没有申请成人用品特种经营许可证的店铺&#xff0c;在淘宝上是发布不了成人用品类目宝贝的&#xff0c;那么&#xff0c;有的店主就有疑问了&#xff1a;成人用品特种经营许可证要在哪里去申请…

基于Spring Boot+Vue+MySQL的理财平台的设计与实现

目 录 摘 要 I Abstract II 目 录 III 图清单 V 表清单 VII 1 绪论 1 1.1 理财平台的现状与发展 1 1.2吾爱理财平台的研究内容 2 1.3 吾爱理财平台的研究目的和意义 2 1.4 本章小结 3 2 本吾爱理财平台的分析 4 2.1 可行性分析 4 2.2 需求分析 4 2.3 框架介绍 6 2.4 本章小结 …

商用、无版权图片素材网站,赶紧马住。

很多朋友不知道去哪里找图片素材&#xff0c;网上找的质量不高先不说&#xff0c;就怕使用不当造成侵权。今天给大家分享6个可商用&#xff0c;还高质量的图片素材网站。1、菜鸟图库 https://www.sucai999.com/pic.html?vNTYwNDUx菜鸟图库网站素材类型很多&#xff0c;像设计、…

【Python游戏】Python实现一个植物大战僵尸小游戏,非常简单,可以用于做毕业设计哟 | 附源码

前言 halo&#xff0c;包子们下午好 今天给打击整一个植物大战僵尸 无广告版本 哈哈 说实话&#xff0c;现在的小游戏很多都是有广告&#xff0c;多少有点难受 今天给大家直接安排 相关文件 关注小编&#xff0c;私信小编领取哟&#xff01; 当然别忘了一件三连哟~~ 源码点…

一篇博客总结深度学习与反向传播

目录 深度学习的发展过程 深度学习的步骤 定义Neural NetWork 全前向连接 softmax介绍 定义loss函数 定义优化器选择最优参数optimization 反向传播Backpropagation 深度学习介绍 反向传播视频 深度学习的发展过程 perceptron(liner model)感知机——线性模型 perc…

知识图谱-KGE-语义匹配-双线性模型-2018:CP

【paper】 Canonical Tensor Decomposition for Knowledge Base Completion【简介】 这篇是 Facebook 法国巴黎 AI 研究中心发表在 ICML 2018 上的文章&#xff0c;是对传统的张量分解方法 CP&#xff08;Canonical Tensor Decomposition&#xff09;做的分析改进。对传统的几个…

泛微文书定确保电子档案移交接收过程:真实、完整、可用和安全

电子档案的移交接收是电子档案管理流程的重要环节之一。 国家档案局发布的《电子档案移交接收操作规程》中明确了电子档案移交接收的工作流程&#xff0c;规定了电子档案移交接收准备工作和电子档案移交接收操作的要求。 在移交接收过程中&#xff0c;如何快速处理大量的电子…

当软件测试迭代测试时间不够时该如何去做好质量控制呢?

大家好&#xff0c;今天我们一起来聊聊&#xff0c;当我们在工作中尤其是快速迭代版本中测试版本的时间被压缩的很短&#xff0c;甚至不够完成用例执行时怎么去做好质量控制呢&#xff1f; 在我们的日常生活中导致软件测试时间不够的原因有很多&#xff0c;那么在这些不确定的人…

客快物流大数据项目(九十二):ClickHouse的MergeTree系列引擎介绍和MergeTree深入了解

文章目录 ClickHouse的MergeTree系列引擎介绍和MergeTree深入了解 一、MergeTree系列引擎介绍 二、​​​​​​​MergeTree深入了解 1、创建MergeTree表的说明 2、创建MergeTree引擎的表 3、删除MergeTree引擎的表 ClickHouse的MergeTree系列引擎介绍和MergeTree深入了解…

【数据库数据恢复】MySQL数据库误删除未备份的数据恢复案例

MySQL数据库属于关系型数据库。SQL是一种用于操作关系型数据库的结构化语言。关系型数据库就是指在关系模型的基础上建立起来的数据库&#xff0c;是一种借助了集合代数等一些数学方法和数学概念处理数据的数据库。 MySQL数据库具有体积小&#xff0c;速度快&#xff0c;性价比…

【QT开发笔记-基础篇】| 第五章 绘图QPainter | 5.2 界面布局

本节对应的视频讲解&#xff1a;B_站_视_频 https://www.bilibili.com/video/BV1fR4y1k7Kt 上节课&#xff0c;初步展示了本章要实现的效果。本节课开始&#xff0c;就从零新建工程&#xff0c;把效果一一实现 首先先把界面搭建起来&#xff0c;也就是把用到的 Label、ComboB…

R语言实现向量自回归VAR模型

澳大利亚在2008 - 2009年全球金融危机期间发生了这种情况。政府发布了一揽子刺激计划&#xff0c;其中包括2008年12月的现金支付&#xff0c;恰逢圣诞节支出。因此&#xff0c;零售商报告销售强劲&#xff0c;经济受到刺激&#xff0c;收入增加了。 最近我们被客户要求撰写关于…

[附源码]计算机毕业设计JAVA整形美容咨询网站

[附源码]计算机毕业设计JAVA整形美容咨询网站 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

企业为什么要做知识管理?如何进行知识管理?

今天将和大家聊一聊如何通过5大步骤&#xff0c;帮助企业进行知识管理与知识沉淀。 近年来&#xff0c;随着建设的深入&#xff0c;IT不仅成为企业运营的基础&#xff0c;而且在ERP、CRM、OA等信息系统内沉淀的大量知识成为了企业创新的知识源泉&#xff0c;于是知识管理逐渐提…

第十四届蓝桥杯集训——JavaC组第一篇——Eclipse的使用

Eclipse是一个非常经典的开发工具&#xff0c;我们小时候使用的就是这个工具&#xff0c;转眼就这么多年了&#xff0c;依然还在使用&#xff0c;说明这个软件的健壮性还是非常强的。 本博客讲解Eclipse这个IDE的使用&#xff1a; 目录 Eclipse的基础使用 1、常用菜单中英…

C语言有必要学的很深入细致吗?

c语言作为一门高级语言来说&#xff0c;它本身的知识点是很少的&#xff0c;很容易掌握&#xff0c;它没有诸如『类&#xff0c;接口&#xff0c;继承&#xff0c;多态&#xff0c;分派&#xff0c;模板』等等唬人的概念&#xff0c;当然不是说你不能通过c实现这些概念而是这个…

【R语言】计算信息份额模型 - Computes information share component share weights

INTRO 最近又重新开始做一些价格发现相关的研究&#xff0c;目前针对不同市场的同种标的之间价格发现作用的度量&#xff0c;大多采用Hasbrouk&#xff08;1995&#xff09;开发的基于VECM的信息份额模型&#xff0c;通过计算IS指标和CS指标来度量信息份额和价格发现的贡献程度…

【MySQL自学之路】第2天——关系代数计算【理论知识】

目录 前言 基础名词 关系 候选码 关系运算 传统的集合计算&#xff08;二目运算&#xff09; 样例表创建【SQL】 专门的关系运算 后记 销毁已经创建的表 前言 在上一节我们提到了关系型数据库和非关系型数据库之间的关系&#xff0c;我们主要以MySQL关系型数据库为主…

11月更新 | Visual Studio Code Python

我们很高兴地宣布&#xff0c;2022年11月发布的适用于 Visual Studio Code Python 和 Jupyter 扩展现已推出&#xff01; 此版本包括以下改进&#xff1a; 迁移 isort 扩展 Pylance 默认关闭自动导入 Pylint 和 flake8 扩展 用于笔记本单元调试的“Just My Code” 如果您有…

STL常用排序算法、替换算法、拷贝算法(20221207)

STL的常用算法 概述&#xff1a; 算法主要是由头文件<algorithm> <functional> <numeric> 组成。 <algorithm>是所有STL头文件中最大的一个&#xff0c;涉及比较、交换、查找、遍历等等&#xff1b; <functional>定义了一些模板类&#xff0…