算法原理
拓扑排序
- 有向无环图(DAG图):若一个有向图中不存在环,则称为有向无环图
- AOV网:若用DAG图表示一个工程,其顶点表示活动,用有向边<Vi,Vj><V_i,V_j><Vi,Vj>表示活动ViV_iVi必须先于活动VjV_jVj进行,则这种有向图称为顶点表示活动的网络,记为AOV网
- 拓扑排序的定义:拓扑排序为对有向无环图的顶点的一种排序,使得若存在一条从顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面。
- 每个AOV网都有一个或多个拓扑排序序列
- 对AOV网进行拓扑排序的过程:
- 从AOV网中选择一个没有前驱的顶点并输出
- 从网中删除该顶点和所有以它为起点的有向边
- 重复1和2,直到当前的AOV网为空或者当前网中不存在无前驱的顶点为止,后一种情况即图中存在环
关键路径
- AOE网:在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销,称之为用边表示活动的网络,简称AOE网
- AOE网的性质
- 只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始
- 只有在进入某顶点的各有向边所代表的活动都已结束时,该顶点所代表的事件才能发生
- 关键路径:从源点到汇点的所有路径中,具有最大路径长度的路径称为关键路径,并把关键路径上的活动称为关键活动。关键路径的长度即为完成整个工程的最短时间
- 关键路径查找算法:
- 从源点出发,按拓扑排序,求出所有事件vkv_kvk的最早发生时间ve(k)ve(k)ve(k).
ve(k)ve(k)ve(k)为从v1v_1v1到顶点vkv_kvk的最长路径长度,决定了所有从vkv_kvk开始的活动能够开工的最早时间
ve(源点)=0ve(源点)=0ve(源点)=0
ve(k)=Max{ve(j)+Weight(vj,vk)}ve(k)=Max\{ve(j)+Weight(v_j,v_k)\}ve(k)=Max{ve(j)+Weight(vj,vk)},其中vkv_kvk为vjv_jvj的任意后继,Weight(vj,vk)Weight(v_j,v_k)Weight(vj,vk)表示<vj,vk><v_j,v_k><vj,vk>上的权值 - 从汇点出发,按逆拓扑排序,求出所有事件vkv_kvk的最迟发生时间vl(k)vl(k)vl(k).
vl(k)vl(k)vl(k)表示在不推迟整个工程完成的前提下,即保证它的后继事件vjv_jvj在其最迟发生时间vl(j)vl(j)vl(j)能够发生时,该事件最迟必须发生的时间。
vl(汇点)=ve(汇点)vl(汇点)=ve(汇点)vl(汇点)=ve(汇点)
vl(k)=Min{vl(j)−Weight(vk,vj)}vl(k)=Min\{vl(j)-Weight(v_k,v_j)\}vl(k)=Min{vl(j)−Weight(vk,vj)},其中vkv_kvk为vjv_jvj的任意前驱 - 根据ve(i)ve(i)ve(i)的值,求出活动aia_iai的最早开始时间e(i)e(i)e(i).
e(i)e(i)e(i)是指该活动的起点所表示的事件的最早发生时间,若边<vk,vj><v_k,v_j><vk,vj>表示活动aia_iai,则有e(i)=ve(k)e(i)=ve(k)e(i)=ve(k) - 根据vl(i)vl(i)vl(i)的值,求出活动aia_iai的最迟开始时间l(i)l(i)l(i).
l(i)l(i)l(i)是指该活动的终点所表示的时间的最迟发生时间与该活动所需时间之差,若边<vk,vj><v_k,v_j><vk,vj>表示活动aia_iai,则有l(i)=vl(j)−Weight(vk,vj)l(i)=vl(j)-Weight(v_k,v_j)l(i)=vl(j)−Weight(vk,vj) - 计算一个所有活动aia_iai的最迟开始时间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)
- 第5步计算的差额即为该活动完成的时间余量,即在不增加完成整个工程所需总时间的情况下,活动aia_iai可以拖延的时间。所以d(i)d(i)d(i)为0的活动aia_iai是关键活动
基于邻接表的代码实现
结构体定义
#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];
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){
list[top] = dig[j].vertex;
print(dig, j, len);
}
q = q->next;
}
top--;
}
按拓扑排序计算活动aia_iai的最早开始时间e(i)e(i)e(i)、按逆拓扑排序计算活动aia_iai的最迟开始时间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++){
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){
j = q->adjvex;
dig[j].indegree--;
if(dig[i].ee + q->dut > dig[j].ee)
dig[j].ee = dig[i].ee + q->dut;
if(dig[q->adjvex].indegree == 0){
stack[top] = q->adjvex;
top++;
}
q = q->next;
}
}
if(top == e_n){
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];
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;
}
运行结果
