一.先给出几个概念:
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:\n");
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>\n",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:\n");
scanf("%d%d",&n,&e); //输入n和e
buildAdjlist(A,n,e); //建立邻接表
printf("\n");
CPM(A,n);
return 0;
}