1、任务简述:
设计实现AOE网的关键活动与关键路径问题
要求:
(1)自行建立图的数据文件,以邻接表或者邻接矩阵表示图皆可,显示输出原图(按照邻接表的样式);
(2)计算出各个事件的最早发生时间与最迟发生时间,并显示输出;
(3)输出所有的关键路径。
2、算法描述:
数据结构:
typedef struct arc
{
int index; //编号
float weight; //权重
struct arc *next; //指向下一个节点
}AR;
typedef struct MyGraph
{
int type;//0表示无向网,1表示有向网
int arcnum; //边的数量
int vexnum; //点的个数
char **vexname; //点的名字
AR *N;//邻接表
}GH;
1. 建立AOE网的存储结构。
2. 拓扑排序,并求得ve[]。从源点V0出发,令ve[0]=0,按拓扑有序求其余各顶点的最早发生时间ve[i]。如果得到的拓扑有序序列中顶点个数小于网中顶点数n,则说明网中存在环,不能求关键路径,算法终止;否则执行步骤3。
3. 拓扑逆序,求得vl[]。从汇点Vn出发,令vl[n-1] = ve[n-1],按逆拓扑有序求其余各顶点的最迟发生时间vl[i]。
4. 求得关键路径。根据各顶点的ve和vl值,求每条弧s的最早开始时间e(s)和最迟开始时间l(s)。若某条弧满足条件e(s) = l(s),则为关键活动。
3、源代码
#include
#include
#include
int flag=0;
typedef struct arc
{
int index; //编号
float weight; //权重
struct arc *next; //指向下一个节点
}AR;
typedef struct MyGraph
{
int type;//0表示无向网,1表示有向网
int arcnum; //边的数量
int vexnum; //点的个数
char **vexname; //点的名字
AR *N;//邻接表
}GH;
int findvex(GH *G,char *s);//确定顶点s对应的编号号
void creatgraph(GH *G);//以邻接表的形式创建图
void showgraph(GH *G);//以邻接表的形式显示图
int tuopu(GH *G,int *t);//获得拓扑排序
void keypath(GH *G); //算出每个点的最早发生时间和最晚发生时间
void allkeypath(GH *G,int head,int end,int *path,int n,int length,int *visit,int *isequl);//显示所有关键路径(不止一条)
main()
{
GH G;
system("color 1E");
printf("--------------081810221朱林昊--------------\n");
printf("\n--------------关键路径--------------\n\n"); //说明该代码的实现功能
printf("\n原图为:\n");
creatgraph(&G);//创建图
showgraph(&G);//显示图
keypath(&G);
}
int findvex(GH *G,char *s)
{
int i;
for(i=0;i<G->vexnum;i++)
{
if(strcmp(G->vexname[i],s)==0)//判断字符串是否一样
return i;
}
printf("Error!\n");
exit(0);
}
void creatgraph(GH *G)
{
FILE *fp;
int i,j,n;
float k;//记录权重
char s1[20],s2[20];
AR *p;
fp=fopen("graph2.txt","rb");
if(!fp)
{
printf("Can not open file!!!\n");
exit(0);
}
fscanf(fp,"%d",&n);
G->vexnum=n; //点的数量
G->type=1; //有向图
G->N=(AR *)malloc(n*sizeof(AR));
G->vexname=(char **)malloc(n*sizeof(char *));
G->arcnum=0; //边的数量初始化为0
for(i=0;i<n;i++)//初始化
{
fscanf(fp,"%s",s1);
G->vexname[i]=(char *)malloc(strlen(s1)*sizeof(char));
strcpy(G->vexname[i],s1);
G->N[i].next=NULL;
}
while(fscanf(fp,"%s%s%f",s1,s2,&k)!=EOF)//读入边的两个顶点和权重
{
p=(AR *)malloc(sizeof(AR));
i=findvex(G,s1);
j=findvex(G,s2);
(G->arcnum)++;
p->index=j;
p->weight=k;
p->next=G->N[i].next;
G->N[i].next=p;
if(G->type==0)
{
p=(AR *)malloc(sizeof(AR));
p->index=i;
p->weight=k;
p->next=G->N[j].next;
G->N[j].next=p;
}
}
fclose(fp);//关闭文件
}
void showgraph(GH *G)//用邻接表显示图
{
int i;
AR *p;
for(i=0;i<G->vexnum;i++)
{
printf("\n%s",G->vexname[i]);
p=G->N[i].next;
while(p)
{
printf("---%s(%.2f)",G->vexname[p->index],p->weight);
p=p->next;
}
printf("\n");
}
printf("\n");
}
int tuopu(GH *G,int *t)
{
int *zhan,*rudu;//zhan是栈,将入度为0的点入栈,rudu是记录每个点入度的数组,用来寻找入度为0的点
int top=-1,count=0,i;//top为栈顶,count表示
AR *p;
zhan=(int *)malloc(G->vexnum*sizeof(int));
rudu=(int *)malloc(G->vexnum*sizeof(int));
memset(rudu,0,G->vexnum*sizeof(int));
for(i=0;i<G->vexnum;i++)//求每个点的入度
{
p=G->N[i].next;
while(p)
{
rudu[p->index]++;
p=p->next;
}
}
for(i=0;i<G->vexnum;i++)//将入度为0的点入栈
{
if(rudu[i]==0)
{
top++;
zhan[top]=i;
}
}
while(top>=0)
{
i=zhan[top];
top--;
t[count]=i;
count++;
p=G->N[i].next;
while(p)//将所有该点连接的点的入度都减一
{
rudu[p->index]--;
if(rudu[p->index]==0)//发现减完后,有点入度为0,入栈
{
top++;
zhan[top]=p->index;
}
p=p->next;
}
}
free(zhan);
free(rudu);
if(count==G->vexnum)//判断是否所有点都在拓扑排序里面,是的话返回1,否则因为没有所有点进入排序,说明排序失败:返回0
return 1;
else
return 0;
}
void keypath(GH *G)
{
int *t,*ve,*vl; //t:记录拓扑序列,ve:最早开始时间,vl:最迟开始时间
int *visit,*path,*isequl;//visit记录每个点是否被访问,path记录路径上的点,isok记录每个点最早开始时间和最迟是否相等,1代表相等,0代表不等
int i;
int max;//用来寻找最早开始时间的最大值
AR *p;
t=(int *)malloc(G->vexnum*sizeof(int));
ve=(int *)malloc(G->vexnum*sizeof(int));
vl=(int *)malloc(G->vexnum*sizeof(int));
visit=(int *)malloc(G->vexnum*sizeof(int));
path=(int *)malloc(G->vexnum*sizeof(int));
isequl=(int *)malloc(G->vexnum*sizeof(int));
memset(ve,0,G->vexnum*sizeof(int));
memset(visit,0,G->vexnum*sizeof(int));
memset(isequl,0,G->vexnum*sizeof(int));
if(!tuopu(G,t))//拓扑排序并输出拓扑序列
{
printf("图中有回路,不存在关键路径!\n");
exit(0);
}
//printf("\n该图的拓扑序列是: "); //输出拓扑路径,没有输出
//for(i=0;ivexnum;i++)
//printf("%s ",G->vexname[t[i]]);
//printf("\n\n");
for(i=0;i<G->vexnum;i++)//求每个点的最早开始时间并输出,从前往后
{
p=G->N[t[i]].next;
while(p)
{
if(ve[p->index]<(ve[t[i]]+p->weight))//判断更新最小的时间
ve[p->index]=ve[t[i]]+p->weight;
p=p->next;
}
}
printf("\n最早发生时间:\n");
max=ve[0];
for(i=0;i<G->vexnum;i++)
{
if(ve[i]>max)
max=ve[i];
printf("%s: %d\n",G->vexname[i],ve[i]);
}
printf("\n");
for(i=0;i<G->vexnum;i++)//给每个点的最迟开始时间赋初值,为最早开始时间
vl[i]=max;
for(i=G->vexnum-1;i>=0;i--)//求每个点的最迟开始时间并输出,思路同上,改变>即可
{
p=G->N[t[i]].next;
while(p)
{
if(vl[t[i]]>(vl[p->index]-p->weight))
vl[t[i]]=vl[p->index]-p->weight;
p=p->next;
}
}
printf("最迟发生时间:\n");
for(i=0;i<G->vexnum;i++)
{
printf("%s: %d\n",G->vexname[i],vl[i]);
if(vl[i]==ve[i])//将最早开始时间和最迟开始时间相等的点的isequal记为1,寻找关键路径
isequl[i]=1;
}
printf("\n");
path[0]=t[0];
allkeypath(G,t[0],t[G->vexnum-1],path,1,0,visit,isequl);//递归调用allkeypath函数显示所有关键路径
}
void allkeypath(GH *G,int head,int end,int *path,int n,int length,int *visit,int *isequl)//path用来存储路径,这个仿照了之前迷宫的路径输出方式
{
if(head==end)//到达终点
{
int i;
flag++;
printf("第%d条关键路径:",flag);
for(i=0;i<n-1;i++)
printf("%s-->",G->vexname[path[i]]);
printf("%s 路径长度为:%d\n\n",G->vexname[path[i]],length);
}
else
{
AR *p;
p=G->N[head].next;
while(p)//遍历每个点的所有后继
{
if(!visit[p->index]&&isequl[p->index])
{
path[n]=p->index;
visit[p->index]=1;
allkeypath(G,p->index,end,path,n+1,length+p->weight,visit,isequl);//递归调用自己,显示所有关键路径
visit[p->index]=0;
}
p=p->next;
}
}
}//共267行
4、运行结果
5、总结
性能分析:
时间复杂度:如果AOV网络有n个顶点,e条边,在拓扑排序的过程中,搜索入度为零的顶点所需的时间是O(n)。在正常情况下,每个顶点进一次栈,出一次栈,所需时间O(n)。每个顶点入度减1的运算共执行了e次。所以总的时间复杂为O(n+e)。
空间复杂度:O(n),存储拓扑路径
心得体会:
运行结果正确,成功创建图,并且找出其拓扑路径以及关键路径。并且这道题我用上次作业题来进行测试,发现运行结果正确,但是要注意,不能简单的把最早开始时间的最后一个直接赋值给最晚开始时间,因为v6不一定是终点,想这题,v3才是终点,所以在输出最早开始时间时,可以来一个遍历,判断,找出最大值。