关键路径 详解 (前置知识:拓扑排序)

图论之关键路径讲解

回顾所需知识:

拓扑排序

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。

拓扑排序对应施工的流程图具有特别重要的作用,它可以决定哪些子工程必须要先执行,哪些子工程要在某些工程执行后才可以执行。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。

通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网

一个AOV网应该是一个有向无环图,即不应该带有回路,因为若带有回路,则回路上的所有活动都无法进行(对于数据流来说就是死循环)。在AOV网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列(Topological order),由AOV网构造拓扑序列的过程叫做拓扑排序(Topological sort)。

AOV网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。

拓扑排序的实现步骤※

  1. 在有向图中选一个没有前驱的顶点并且输出
  2. 从图中删除该顶点和所有以它为尾的弧 (白话就是:删除所有和它有关的边)
  3. 重复上述两步,直至所有顶点输出,循环结束后,若输出的顶点数小于网中的顶点数,则输出“有回路”信息,否则输出的顶点序列就是一种拓扑序列。

因此,也可以通过拓扑排序来判断一个图是否有环。

AOE网介绍

AOE-网只是比AOV-网多了一个边的权重,而且AOV-网一般是设计一个庞大的工程各个子工程实施的先后顺序,而我们的AOE-网就是不仅仅关系整个工程中各个子工程的实施的先后顺序,同时也关系整个工程完成最短时间。

通常在AOE网中列出完成预定工程计划所需要进行的活动,每个活动计划完成的时间,要发生哪些事件以及这些事件与活动之间的关系,从而可以确定该项工程是否可行,估算工程完成的时间以及确定哪些活动是影响工程进度的关键

AOE网特点

AOE-网还有一个特点就是:只有一个起点(入度为0的顶点)和一个终点(出度为0的顶点),并且AOE-网有两个待研究的问题:
1、完成整个工程需要的时间
2、哪些活动是影响工程进度的关键

关键路径介绍

关键路径:AOE-网中,从起点到终点最长的路径的长度(长度指的是路径上边的权重和)
关键活动
假设起点是vo,则我们称从v0到vi的最长路径的长度为vi的最早发生时间
同时,vi的最早发生时间也是所有以vi为尾的弧所表示的活动的最早开始时间
使用e(i)表示活动ai最早发生时间,除此之外,我们还定义了一个活动最迟发生时间,使用l(i)表示,不推迟工期的最晚开工时间。我们把e(i)=l(i)的活动ai称为关键活动,因此,这个条件就是我们求一个AOE-网的关键路径的关键所在了。
AOE网

求关键路径的步骤

  1. 输入顶点数和边数,已经各个弧的信息建立图
  2. 从源点v1出发,令ve[0]=0;按照拓扑序列往前求各个顶点的ve。如果得到的拓扑序列个数小于网的顶点数n,说明我们建立的图有环,无关键路径,直接结束程序
  3. 从终点vn出发,令vl[n-1]=ve[n-1],按逆拓扑序列,往后求其他顶点vl值
  4. 根据各个顶点的ve和vl求每个弧的e(i)和l(i),如果满足e(i)=l(i),说明是关键活动。

代码实现

例题
SDUT2498

#include<bits/stdc++.h>

using namespace std;

const int M = 5e4 + 5;

struct N {
	int in, out, weight;
}edge[M];				//用结构体表示边

int node[M];			//记录经过的节点
int distant[M];			//记录最长路径
int degree_in[M];		//记录每个点的入度   目的是寻找源点(没有入度的点)
int sourcepoint;		//源点	

void bellman(int point_number, int edge_number) {
	memset(node, 0, sizeof(node));
	memset(distant, 0, sizeof(distant));
	for (int k = 2; k <= point_number; k++) {		//除去源点,只需要进行(点的数量-1)次求值
		int flag = 0;								//每次找下一个点的时候都会重置flag
		for (int i = 1; i <= edge_number; i++) {		//动态规划,找K次,每次都更新源点的权值,最后源点的权值最大,输出的就是最大路径
			if ((distant[edge[i].in] < distant[edge[i].out] + edge[i].weight) || ((distant[edge[i].in] == distant[edge[i].out] + edge[i].weight) && (edge[i].out < node[edge[i].in]))) {
				distant[edge[i].in] = distant[edge[i].out] + edge[i].weight;	//把权值放到每条边的起点上,最后输出源点的权值
				node[edge[i].in] = edge[i].out;
				flag = 1;
			}
		}
		if (flag == 0) {							//如果flag没更新,说明循环每条边的时候找不到比原来的权值更大的路径了,跳出循环即可
			break;									//这时候源点处已经找到了最大权值
		}
	}
	printf("%d\n", distant[sourcepoint]);			//跳出循环后,输出最终结果
	while (node[sourcepoint] != 0) {
		printf("%d %d\n", sourcepoint, node[sourcepoint]);
		sourcepoint = node[sourcepoint];
	}
}

int main() {
	int point_number, edge_number;
	int sv, ev, w;
	while (~scanf("%d%d", &point_number, &edge_number)) {
		memset(edge, 0, sizeof(edge));				//多组数据输入时 初始化很重要
		memset(degree_in, 0, sizeof(degree_in));
		for (int i = 1; i <= edge_number; i++) {
			scanf("%d%d%d", &sv, &ev, &w);
			edge[i].in = sv;		//每条边的入度、出度、权重
			edge[i].out = ev;
			edge[i].weight = w;
			degree_in[ev]++;		//每个点的入度
		}
		for (int i = 1; i <= point_number; i++) {		//寻找源点
			if (degree_in[i] == 0) {						//入度为零的点为源点
				sourcepoint = i;
			}	
		}
		bellman(point_number, edge_number);
	}
	return 0;
}

看不懂的可以自取视频理解,多看两遍就差不多了

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值