关键路径(拓扑排序)

一.先给出几个概念:

AOE-网:在带权有向图中顶点表示事物,有向边表示活动,权表示活动持续的时间,则此有向图称为边表示活动的网络。(Activity On Edge Network)

(表示实际工程的AOE-网应该是无环的,且存在唯一入度为0的起始顶点(始点),以及唯一出度为0的完成顶点(终点)。)

利用AOE-网进行工程安排的估算,可以实现:

1.研究完成工程至少需要多少时间。

2.为缩短时间,应加快哪些活动的速度等问题。
用e(i)表示活动ai的最早开始时间,l(i)表示ai的最迟开始时间,e(i)=l(i)的活动称为关键活动,由这些关键活动连接起来的路径称为关键路径。

二.原理:

问题在于如何求e(i),l(i)。

若Ve(i):事件i的最早发生时间;Vl(i):事件最迟发生时间。(注意区分,活动表示边,时间表示顶点,e(i),l(i)是对边而言,Ve(i),Vl(i)是对顶点而言。

故可将对e(i),l(i)的计算转化成对Ve,Vl的计算,而对Ve,Vl的计算可以通过拓扑排序的过程来实现。

e(i)=Ve(j)          

l(i)=Vl(k)-dut(<j,K>)

三.具体实现:

1.这里对图的存储使用邻接表,因为临界矩阵很难实现,他无法体现边的性质。

但需要对邻接表做些改动:在表头结点中加一项index,用于存放入度,并同时作为拓扑栈和逆拓扑栈。当然也可以不这样做,但会耗费一定的空间。代码如下:

#include<cstdio>
#include<cstdlib>
#define LEN sizeof(tablenode)
using namespace std;

typedef struct node1{                   //定义表结点tablenode
	int adjvex;                         //顶点
	int dut;                            //边的权值
	struct node1* next;                 //指向下一个表结点的指针
}tablenode;

typedef struct node2{                   //定义表头结点header
	int index;                          //用作存放入度和拓扑栈、逆拓扑栈
 	int vertex;                         //顶点
	struct node1* out;                  //指向该顶点的出边
}header;

2.建立邻接表:

我这里图的顶点即采用0..n-1(n为顶点个数),但输入边的时候是输的1..n,所以建立邻接表需要减一。另外,在建立的同时需要计算出每个顶点的入度,为了后面拓扑排序时方便。代码如下:

void buildAdjlist(header (&A)[101],int n,int e){         //建立邻接表
	for(int i=0;i<n;i++){               //初始化邻接表
		A[i].index=0;                   //入度初始化为0
		A[i].vertex=i;                  //顶点初始化为i
		A[i].out=NULL;                  //指向出边的指针初始化为NULL
	}
	for(int i=0;i<e;i++){
		int start,end,dut;
		scanf("%d%d%d",&start,&end,&dut);  //输入边的始点,终点,权值
		start--;
		end--;                          //将输入的点转换成数组下标
		tablenode* P=(tablenode*)malloc(LEN);
		P->adjvex=end;
		P->dut=dut;
		P->next=A[start].out;
		A[start].out=P;                 //在A[start]的表中插入P
		A[end].index++;	                //A[end]的入度加一
	}
}

3.求关键路径:

这里大概有3步:根据拓扑排序计算Ve,根据拓扑排序的序列反向进行逆拓扑排序计算Vl,输出关键路径。

具体的实现看如下的代码:

void CPM(header (&A)[101],int n){
	int ftop=-1,btop=-1;                //拓扑栈,逆拓扑栈栈顶指针初始化为-1
	int m=0;                            //拓扑排序计数器
	int Ve[101],Vl[101];                //记录顶点的最快发生时间,最迟发生时间
	for(int i=0;i<n;i++){
		if(A[i].index==0){
			A[i].index=ftop;            //将入度为0的顶点入栈
			ftop=i;
		}	
		Ve[i]=0; 	                    //最快发生时间为0
		
	}
	while(ftop!=-1){                    //拓扑排序
		m++;
		int p=ftop;
		ftop=A[ftop].index;             //拓扑栈出栈
		A[p].index=btop;
		btop=p;							//逆拓扑栈入栈
		tablenode* k=A[p].out;
		while(k!=NULL){
			int x=k->adjvex;
			A[x].index--;               //入度减一
			if(A[x].index==0){
				A[x].index=ftop;
				ftop=x;                 //若入度为0则入栈
			}
			if(Ve[x]<Ve[p]+k->dut)
				Ve[x]=Ve[p]+k->dut;     //若通过始点p能增大终点x的Ve则增大
			k=k->next;			
		}
	}
	if(m<n){                            //若m<n,则该图有环
		printf("Error:The network has a cycle!");
		return;
	}
	int endnode=btop; 
	Vl[endnode]=Ve[endnode];            //最后一个顶点的Ve=Vl
	while(btop!=-1){                    //逆拓扑排序
		int p=btop;
		btop=A[btop].index;             //逆拓扑栈出栈
		Vl[p]=Vl[endnode];              //因为是取最小值,故Vl均初始化为最大值
		tablenode* k=A[p].out;
		while(k!=NULL){
			int x=k->adjvex;
			if(Vl[p]>Vl[x]-k->dut)
				Vl[p]=Vl[x]-k->dut;     //若通过终点k能减小始点p的Vl则减小
			k=k->next;
		}
	}
	printf("Critical Pathes are as follows:
");
	for(int i=0;i<n;i++){               //输出关键路径
		tablenode* k=A[i].out;
		while(k!=NULL){
			int x=k->adjvex;
			if(Ve[i]==Vl[x]-k->dut)
				printf("<%d,%d>
",i+1,x+1);
			k=k->next;
		}
	}
}

4.主函数如下:

int main(){
	header A[101];	                    //定义邻接表
	int n,e;                            //顶点数n,边的条数e
	printf("Please enter the number of vertex and side:
");
	scanf("%d%d",&n,&e);                //输入n和e
	buildAdjlist(A,n,e);                //建立邻接表
 	printf("
");
	CPM(A,n);
	return 0;
}
原文地址:https://www.cnblogs.com/FrankChen831X/p/10326082.html