数据结构——关键路径查找

算法原理

拓扑排序
  • 有向无环图(DAG图):若一个有向图中不存在环,则称为有向无环图
  • AOV网:若用DAG图表示一个工程,其顶点表示活动,用有向边 < V i , V j > <V_i,V_j> <Vi,Vj>表示活动 V i V_i Vi必须先于活动 V j V_j Vj进行,则这种有向图称为顶点表示活动的网络,记为AOV网
  • 拓扑排序的定义:拓扑排序为对有向无环图的顶点的一种排序,使得若存在一条从顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面。
  • 每个AOV网都有一个或多个拓扑排序序列
  • 对AOV网进行拓扑排序的过程:
    1. 从AOV网中选择一个没有前驱的顶点并输出
    2. 从网中删除该顶点和所有以它为起点的有向边
    3. 重复1和2,直到当前的AOV网为空或者当前网中不存在无前驱的顶点为止,后一种情况即图中存在环
关键路径
  • AOE网:在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销,称之为用边表示活动的网络,简称AOE网
  • AOE网的性质
    • 只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始
    • 只有在进入某顶点的各有向边所代表的活动都已结束时,该顶点所代表的事件才能发生
  • 关键路径:从源点到汇点的所有路径中,具有最大路径长度的路径称为关键路径,并把关键路径上的活动称为关键活动。关键路径的长度即为完成整个工程的最短时间
  • 关键路径查找算法:
    1. 从源点出发,按拓扑排序,求出所有事件 v k v_k vk的最早发生时间 v e ( k ) ve(k) ve(k).
      v e ( k ) ve(k) ve(k)为从 v 1 v_1 v1到顶点 v k v_k vk的最长路径长度,决定了所有从 v k v_k vk开始的活动能够开工的最早时间
      v e ( 源 点 ) = 0 ve(源点)=0 ve()=0
      v e ( k ) = M a x { v e ( j ) + W e i g h t ( v j , v k ) } ve(k)=Max\{ve(j)+Weight(v_j,v_k)\} ve(k)=Max{ve(j)+Weight(vj,vk)},其中 v k v_k vk v j v_j vj的任意后继, W e i g h t ( v j , v k ) Weight(v_j,v_k) Weight(vj,vk)表示 < v j , v k > <v_j,v_k> <vj,vk>上的权值
    2. 从汇点出发,按逆拓扑排序,求出所有事件 v k v_k vk的最迟发生时间 v l ( k ) vl(k) vl(k).
      v l ( k ) vl(k) vl(k)表示在不推迟整个工程完成的前提下,即保证它的后继事件 v j v_j vj在其最迟发生时间 v l ( j ) vl(j) vl(j)能够发生时,该事件最迟必须发生的时间。
      v l ( 汇 点 ) = v e ( 汇 点 ) vl(汇点)=ve(汇点) vl()=ve()
      v l ( k ) = M i n { v l ( j ) − W e i g h t ( v k , v j ) } vl(k)=Min\{vl(j)-Weight(v_k,v_j)\} vl(k)=Min{vl(j)Weight(vk,vj)},其中 v k v_k vk v j v_j vj的任意前驱
    3. 根据 v e ( i ) ve(i) ve(i)的值,求出活动 a i a_i ai的最早开始时间 e ( i ) e(i) e(i).
      e ( i ) e(i) e(i)是指该活动的起点所表示的事件的最早发生时间,若边 < v k , v j > <v_k,v_j> <vk,vj>表示活动 a i a_i ai,则有 e ( i ) = v e ( k ) e(i)=ve(k) e(i)=ve(k)
    4. 根据 v l ( i ) vl(i) vl(i)的值,求出活动 a i a_i ai的最迟开始时间 l ( i ) l(i) l(i).
      l ( i ) l(i) l(i)是指该活动的终点所表示的时间的最迟发生时间与该活动所需时间之差,若边 < v k , v j > <v_k,v_j> <vk,vj>表示活动 a i a_i ai,则有 l ( i ) = v l ( j ) − W e i g h t ( v k , v j ) l(i)=vl(j)-Weight(v_k,v_j) l(i)=vl(j)Weight(vk,vj)
    5. 计算一个所有活动 a i a_i ai的最迟开始时间 l ( i ) l(i) l(i)和最早开始时间 e ( i ) e(i) e(i)的差额 d ( i ) = l ( i ) − e ( i ) d(i)=l(i)-e(i) d(i)=l(i)e(i)
    6. 第5步计算的差额即为该活动完成的时间余量,即在不增加完成整个工程所需总时间的情况下,活动 a i a_i ai可以拖延的时间。所以 d ( i ) d(i) d(i)为0的活动 a i a_i ai是关键活动

基于邻接表的代码实现

结构体定义
#include<stdio.h>
#include<stdlib.h>

typedef struct VexNode{
	int adjvex;
	int dut;
	struct VexNode *next;
}VexNode;

typedef struct ENode{
	int indegree;
	int vertex;
	int ee, el;
	struct VexNode *link;
}ENode;
打印关键路径
void print(ENode dig[], int first, int len){
	int i, j;
	static int top=0, list[50];		// static定义的变量在函数执行完成后不会被释放,下一次调用函数时,会保留上次的值继续使用
	VexNode *q;
	i = first;
	q = dig[i].link;
	list[top] = dig[i].vertex;
	top++;
	if(q == NULL){
		printf("%d", list[len]);
		for(i=1+len; i<top; i++){
			printf("->%d", list[i]);
		}
		printf("\n");
	}
	while(q != NULL){
		j = q->adjvex;
		if(dig[j].ee == dig[j].el){			// 如果ve(j)=vl(j),则j为关键路径上的事件
			list[top] = dig[j].vertex;
			print(dig, j, len);
		}
		q = q->next;
	}
	top--;
}
按拓扑排序计算活动 a i a_i ai的最早开始时间 e ( i ) e(i) e(i)、按逆拓扑排序计算活动 a i a_i ai的最迟开始时间 l ( i ) l(i) l(i)
int TopoSort(ENode dig[],int e_n,int stack[]){
	int top=0, bottom=0, len=0;
	int i, j;
	VexNode *q;
	for(int i=1; i<=e_n; i++){
		// 入度为0的结点入栈
		if(dig[i].indegree==0){
			stack[top] = i;
			top++;
		}
	}
	len = top;
	while(top > bottom){
		i = stack[bottom];
		q = dig[i].link;
		bottom++;				// 去除栈底结点
		while(q != NULL){		// 栈底结点的所有邻接结点的入度减一,同时按拓扑排序计算ve(k)
			j = q->adjvex;
			dig[j].indegree--;
			if(dig[i].ee + q->dut > dig[j].ee)		// q->dut为边<i, j>的权值
				dig[j].ee = dig[i].ee + q->dut;
			if(dig[q->adjvex].indegree == 0){		// 如果此时结点j的入度为0,则入栈
				stack[top] = q->adjvex;
				top++;
			}
			q = q->next;
		}
	}
	if(top == e_n){									// 按逆拓扑排序计算vl(k)
		for(i=1; i<=e_n; i++){
			dig[i].el = dig[stack[top-1]].ee;
		}
		bottom = 0;
		while(bottom < top){
			top--;
			i = stack[top];
			q = dig[i].link;
			while(q != NULL){
				j = q->adjvex;
				if(dig[j].el - q->dut < dig[i].el)
					dig[i].el = dig[j].el - q->dut;
				q = q->next;
			}
		}
		for(i=0; i<len; i++){
			print(dig, stack[i], i);
		}
		return 0;
	}else{
		return 1;
	}
}
主函数
int main(){
	ENode dig[20];
	VexNode *q;
	char ch;
	int e_n, v_n, sign, i, j, u, v, stack[20];		// stack中元素按图的拓扑排序存储
	printf("请输入顶点个数和边的条数:");
	scanf("%d%*c%d",&e_n,&v_n);
	// 初始化图
	for (i=1; i<=e_n; i++)
	{
		dig[i].indegree = 0;
		dig[i].link = NULL;
		dig[i].vertex = i;
		dig[i].ee = dig[i].el = 0;
	}
	// 通过输入构造图
	for (i=0; i<v_n; i++)
	{
	    printf("请输入NO.%d条边的起始点以及该边的权:\n",i+1);
		scanf("%d%*c%d%*c%d",&u,&v,&j);
		q=(VexNode *)malloc(sizeof(VexNode));
		dig[v].indegree++;
		q->adjvex = v;
		q->dut = j;
		q->next = dig[u].link;			// 头插法
		dig[u].link = q;
	}
	sign = TopoSort(dig, e_n, stack);
	if (sign) 
	    printf("图中存在环,不存在关键路径\n");
	return 0;
}
运行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值