一、问题描述
设计实现 AOE 网的关键活动与关键路径问题;
二、算法思想
- 获取拓扑序列;
- 计算节点的最早开始时间 v e [ i ] ve[i] ve[i];
- 计算节点的最晚开始时间 v l [ j ] vl[j] vl[j];
- 查找关键路径
三、代码实现
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define maxx 999999
#pragma warning(disable:4996)
typedef struct arc//弧
{
int index;//指向节点编号
int weight;//边的权值
struct arc *next;
}AR;
typedef struct MyGraph
{
int type;//0表示无向图,1表示有向图
int arcnum,vexnum;//边的个数、顶点个数
char **vexname;//存放顶点名称的二维数组
AR *N;//表头数组
int **A;//邻接矩阵
}GH;
int findvex(char *s,GH *G);//找到图G中名称为s的节点编号,并将其返回
void createGraph(GH *G);//创建图G
void showGraph(GH *G);//以邻接表的形式显示图G
int Topological_sort(GH *G,int *q);//对G进行拓扑排序,q用于存放拓扑序列;如果G中有回路,返回1,否则返回0
void CriticalPath(GH *G);//寻找G中的关键路径
void findCP(int *t,int top,int end,int *visit,GH *G,int *ve,int *vl,int *count);//递归函数,用于在图G中寻找关键路径;
//t为栈,存储关键路径上节点编号;end表示汇点编号;visit表示访问标记数组;ve,vl表示节点的最早开始时间和最晚开始时间;count表示关键路径条数;
int main(void)
{
GH *G;
G=(GH *)malloc(sizeof(GH));
createGraph(G);
printf("图的邻接表形式:\n");
showGraph(G);
CriticalPath(G);
free(G);
return 0;
}
int findvex(char *s,GH *G)//找到图G中名称为s的节点编号,并将其返回
{
int i;
for(i=0;i<G->vexnum;i++)
{
if(strcmp(s,G->vexname[i])==0)
return i;
}
printf("读取文件错误.\n");
exit(-1);
}
void createGraph(GH *G)//创建图G
{
int i,j,n,edge;
char filename[]="graph.txt";//存放图的数据文件
char str[10],str1[10];
FILE *fp;
AR *p;
fp=fopen(filename,"r");
if(!fp)
{
printf("打开文件失败!\n");
exit(-1);
}
fscanf(fp,"%d",&G->type);//读取图的类型
G->arcnum=0;
fscanf(fp,"%d",&n);//读取结点数量
G->vexnum=n;
//为动态数组分配空间
G->vexname=(char **)malloc(n*sizeof(char *));
G->N=(AR *)malloc(n*sizeof(AR));
G->A=(int **)malloc(n*sizeof(int *));
//对头结点数组和邻接矩阵初始化
for (i = 0; i < n; i++)
{
G->N[i].next = NULL;
G->A[i] = (int *)malloc(n*sizeof(int));
for (j = 0; j < n; j++)
G->A[i][j]=maxx;
}
//读取顶点名称
for(i=0;i<n;i++)
{
fscanf(fp,"%s",str);
G->vexname[i]=(char *)malloc(strlen(str)*sizeof(char));
strcpy(G->vexname[i],str);
}
//读取边
while(!feof(fp))
{
fscanf(fp,"%s",str);
fscanf(fp,"%s",str1);
fscanf(fp,"%d",&edge);
i=findvex(str,G);
j=findvex(str1,G);
//邻接表
p=(AR *)malloc(sizeof(AR));
p->index=j;
p->weight=edge;
p->next=G->N[i].next;
G->N[i].next=p;
//邻接矩阵
G->A[i][j]=edge;
G->arcnum++;//边的个数增加
if(G->type==0)//如果是无向图
{
//邻接表
p=(AR *)malloc(sizeof(AR));
p->index=i;
p->weight=edge;
p->next=G->N[j].next;
G->N[j].next=p;
//邻接矩阵
G->A[j][i]=edge;
}
}
fclose(fp);
}
void showGraph(GH *G)//以邻接表的形式显示图G
{
int i;
AR *p;//用于遍历
for (i = 0; i < G->vexnum; i++)
{
printf("%s",G->vexname[i]);
p=G->N[i].next;
while (p)
{
if (G->type == 1)
printf("-->");
else//无向图没有箭头
printf("--");
printf("%s(%d)",G->vexname[p->index],p->weight);
p=p->next;
}
printf("\n");
}
printf("\n");
}
int Topological_sort(GH *G,int *q)//对G进行拓扑排序,q用于存放拓扑序列;如果G中有回路,返回1,否则返回0
{
int i,n;
int *d;
int *t,top;
int index,count;
AR *p;
n=G->vexnum;
d=(int *)malloc(n*sizeof(int));//统计各个节点的入度
t=(int *)malloc(n*sizeof(int));//建立栈,用于存储节点编号
top=-1;
//初始化,将各个节点的入度设为0
for (i = 0; i < n; i++)
d[i] = 0;
//遍历表头数组,统计各个节点的入度
for (i = 0; i < n; i++)
{
p=G->N[i].next;
while (p)
{
d[p->index]++;
p=p->next;
}
}
//挑选入度为0的点进栈
for (i = 0; i < n; i++)
{
if (d[i] == 0)
{
top++;
t[top]=i;
}
}
count=0;//统计弹出的节点个数
while (top >= 0)//若栈非空,弹栈
{
index=t[top];//栈顶元素编号
//栈顶元素不输出
//printf("%s ",G->vexname[index]);
top--;
//记录弹出序列,即拓扑序列
q[count]=index;
count++;
//遍历弹出节点的邻接表,其相邻点的入度减一
p=G->N[index].next;
while (p)
{
d[p->index]--;
if (d[p->index] == 0)//若入度变为0,进栈
{
top++;
t[top]=p->index;
}
p=p->next;
}
}
//printf("\n");//输出
free(t);
free(d);
if (count == n)//拓扑序列中含有G中全部点,表示没有回路
return 0;
else
return 1;
}
void CriticalPath(GH *G)//寻找G中的关键路径
{
int i,n;
int x,y;//计数器
int length;//关键路径长度
int num,count;//关键节点个数和关键路径条数
AR *p;
int *q,*ve,*vl;
int *t;//用于在递归寻找关键路径时存储路径
int *visit;//访问标记数组
n=G->vexnum;
q=(int *)malloc(n*sizeof(int));//拓扑序列,存储节点编号
ve=(int *)malloc(n*sizeof(int));//最早开始时间
vl=(int *)malloc(n*sizeof(int));//最晚开始时间
if (Topological_sort(G,q))//获取拓扑序列
{
printf("该有向图中存在回路,故不存在关键路径.\n");
return;
}
//1.计算最早开始时间
//初始化,全设为0
for (i = 0; i < n; i++)
ve[i] = 0;
for (i = 0; i < n; i++)//利用正向拓扑序列计算最早开始时间
{
x = q[i];
p = G->N[x].next;//利用邻接表寻找x的直接后继
while (p)//更新x的直接后继的最早开始时间
{
if (ve[p->index] < ve[x] + (p->weight))
ve[p->index] = ve[x] + (p->weight);
p = p->next;
}
}
//2.计算最晚开始时间
length = ve[q[n - 1]];//关键路径长度
//初始化
for (i = 0; i < n; i++)
vl[i] = length;
for (i = n - 1; i >= 0; i--)//利用逆向拓扑序列计算最晚开始时间
{
y = q[i];
//利用邻接矩阵寻找y的直接前驱
for (x = 0; x < n; x++)//更新y的直接前驱的最晚开始时间
{
if (G->A[x][y] < maxx)//找到之后
{
if (vl[x] > vl[y] - G->A[x][y])
vl[x] = vl[y] - G->A[x][y];
}
}
}
//3.输出最早开始时间和最晚开始时间
num=0;
visit=(int *)malloc(n*sizeof(int));
printf("节点名称 最早开始时间 最晚开始时间\n");
for (i = 0; i < n; i++)
{
x = q[i];//节点编号
printf("%4s %10d %12d\n",G->vexname[x],ve[x],vl[x]);
if (ve[x] == vl[x])
{
visit[x] = 0;//关键节点设置为未访问
num++;
}
else
visit[x]=1;//事先标记非关键节点,避免后续访问
}
//4.利用递归在关键节点中探寻关键路径
//初始化
t = (int *)malloc(num*sizeof(int));//存储关键路径中节点编号
visit[q[0]]=1;
t[0] = q[0];
count = 0;//关键路径条数
//调用递归函数
findCP(t,0,q[n-1],visit,G,ve,vl,&count);
printf("\n");
free(ve);
free(vl);
free(q);
free(t);
free(visit);
}
//t为栈,存储关键路径上节点编号;end表示汇点编号;visit表示访问标记数组;ve,vl表示节点的最早开始时间和最晚开始时间;count表示关键路径条数;
void findCP(int *t,int top,int end,int *visit,GH *G,int *ve,int *vl,int *count)//递归函数,用于在图G中寻找关键路径;
{
int i;
int cur;
AR *p;
cur=t[top];
if (cur == end)//基准情况:到达汇点,输出路径
{
(*count)++;
printf("\n第%d条关键路径:\n",(*count));
for (i = 0; i < top; i++)
printf("%s-->",G->vexname[t[i]]);
printf("%s\n",G->vexname[cur]);
}
else//非基准情况
{
p=G->N[cur].next;
while (p)//遍历当前节点的直接后继
{
if (visit[p->index]==0 && ve[cur] + (p->weight) == vl[p->index])//关键工序(边)的判别条件,非关键节点的visit[i]==1
{
visit[p->index]=1;
t[top+1]=p->index;//入栈
findCP(t,top+1,end,visit,G,ve,vl,count);//调用递归函数
visit[p->index]=0;//撤销标记
}
p=p->next;
}
}
}
四、小结
1、 为了利用拓扑序列计算最早和最迟开始时间,在进行拓扑排序时,对排序序列进行记录;
2、 对于关键路径不唯一的情况,采用 DFS 寻找关键路径,查找路径之前,标记非关键节点,避免后续访问,从而简化了节点添入路径的条件,提高算法的执行效率;
3、 t 用于存储关键路径中的节点编号,由于关键路径中的节点均是关键节点,所以在给 t 分配空间时,分配的空间单位数与关键节点个数相同即可;
4、 在实际计算最早开始时间时,做法并不与算法思想完全一致,具体做法为:先设初值,
v
e
[
i
]
ve[i]
ve[i] 全部为0;然后遍历正向拓扑序列
q
q
q,对于编号为
q
[
i
]
q[i]
q[i] 的节点,更新其直接后继
y
y
y 号节点的最早开始时间,即若
v
e
[
y
]
<
v
e
[
q
[
i
]
]
+
A
[
q
[
i
]
]
[
y
]
ve[y]<ve[q[i]]+A[q[i]][y]
ve[y]<ve[q[i]]+A[q[i]][y],则令
v
e
[
y
]
=
v
e
[
q
[
i
]
]
+
A
[
q
[
i
]
]
[
y
]
ve[y]= ve[q[i]]+ A[q[i]][y]
ve[y]=ve[q[i]]+A[q[i]][y];对于最迟开始时间,进行类似操作;