C语言--关键路径--数据结构
一、问题描述
1.关键路径
通常把计划、施工过程、生产流程、程序流程等都当成一个工程。工程通常分为若干个称为“活动”的子工程。完成了这些“活动”,这个工程就可以完成了。通常用AOE-网来表示工程。AOE-网是一个带权的有向无环图,其中,顶点表示事件(EVENT),弧表示活动,权表示活动持续的时间。
AOE-网可以用来估算工程的完成时间。
由于AOE-网中的有些活动可以并行进行,从开始点到各个顶点,以致从开始点到完成点的有向路径可能不止一条,这些路径的长度也可能不同。完成不同路径的活动所需的时间虽然不同,但只有各条路径上所有活动都完成了,这个工程才算完成。因此,完成工程所需的最短时间是从开始点到完成点的最长路径的长度,即在这条路径上的所有活动的持续时间之和.这条路径长度就叫做关键路径(Critical Path)。可以使人们了解:
(1)研究某个工程至少需要多少时间?
(2)哪些活动是影响工程进度的关键?
二、算法设计
(1)事件的最早发生时间: 即顶点Vk的最早发生时间。
(2)事件的最晚发生时间: 即顶点Vk的最晚发生时间。也就是每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个工期。
(3)活动的最早开工时间: 即弧ak的最早发生时间。
(4)活动的最晚开工时间: 即弧ak的最晚发生时间,也就是不推迟工期的最晚开工时间。然后根据最早开工时间和最晚开工时间相等判断弧是否是关键活动。
由关键活动组成的完整的通路为一条关键路径。
与拓扑序列邻接表结构不同的地方在于,弧链表增加了weight域,用来存储弧的权值。求事件的最早发生时间的过程,就是从头至尾找拓扑序列的过程。
因此,在求关键路径之前,先要调用一次拓扑序列算法的代码来计算事件的最早发生时间和拓扑序列表。
数组ve[]存储事件最早发生时间
数组vl[]存储事件最迟发生时间
三、算法实现
#pragma warning (disable:4996)
#define STACK_INIT_SIZE 100
#define STACKINCREMMENT 10
#define MAX_VERTEX_NUM 20
#define error -1
#define SElemType int
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int aim;
int ve[MAX_VERTEX_NUM] = { 0 }, vl[MAX_VERTEX_NUM] = { 0 };//ve[]:最早发生时间 vl[]:最迟发生时间
int flag = 0;
typedef struct ArcNode
{
int adjvex;//该弧所指向的顶点的位置
struct ArcNode* nextarc;//指向下一条弧的指针
int* info;//该弧相关信息的指针
int dis;
}ArcNode;
typedef struct VNode
{
int data;//顶点信息
ArcNode* firstarc;//指向第一条依附
}VNode,AdjList[MAX_VERTEX_NUM];
typedef struct
{
AdjList vertices;
int vexnum, arcnum;//图的当前顶点数和弧数
}ALGraph;
typedef struct {
int* base;
int* top;
int stacksize;
}SqStack;//栈
InitStack(SqStack* s)
{
s->base = (int*)malloc(100 * sizeof(int));
if (!s->base)exit(-1);
s->top = s->base;
s->stacksize = 100;
}//建栈
int StackEmpty(SqStack* s)
{
if (s->base == s->top)
return 0;
else
return 1;
}
Push(SqStack* s, int e)
{
if (s->top - s->base >= s->stacksize)
{
s->base = (int*)realloc(s->base, (s->stacksize + 10) * sizeof(int));
if (!s->base)exit(-1);
s->top = s->base + s->stacksize;
s->stacksize += 10;
}
*s->top++ = e;
}
int Pop(SqStack* s, int* e)
{
if (s->top == s->base)
{
return -1;
}
(s->top)--;
*e = *(s->top);
}
int Locate(int ver, ALGraph *G)
{
for (int i = 0; i < G->vexnum; i++) {
if (ver == G->vertices[i].data)
return i;
}
return error;
}
InitFistarc(ALGraph* G,int i)
{
ArcNode* a= (ArcNode*)malloc(sizeof(ArcNode));
a->nextarc = NULL;
G->vertices[i].firstarc = a;
return 1;
}
ArcInsert(ALGraph* G, int u, int v, int w)
{
ArcNode* newnode = (ArcNode*)malloc(sizeof(ArcNode));
if (newnode == NULL)
return -1;
newnode->adjvex = v;//因为Locate的到的下标从0开始
newnode->dis = w;
newnode->nextarc = G->vertices[u].firstarc->nextarc;
G->vertices[u].firstarc->nextarc = newnode;
}
CreateDN(ALGraph* G)//有向网
{
printf( "请输入图的顶点数和弧数:\n");
scanf("%d,%d",&G->vexnum,&G->arcnum);
aim = G->vexnum - 1;
for (int i = 0; i < G->vexnum; i++) {
printf("请输入第%d个顶点的信息:\n", i + 1);
scanf("%d",&G->vertices[i].data);
InitFistarc(G,i);
}
int u;
int v;
for (int k = 0; k < G->arcnum; k++)
{
printf( "请输入第%d条边的信息(u,v):\n",k+1);
scanf("%d,%d",&u,&v);
int i = Locate(u,G), j = Locate(v, G);
if (i == -1)
return error;
int w;
printf("请输入权值信息(w):\n");
scanf("%d",&w);
if (!ArcInsert(G, i, j, w))
return 0;
}
return 1;
}
FindInDegree(ALGraph*G, int indegree[MAX_VERTEX_NUM])
{
for (int i = 0; i < G->vexnum; i++) {
ArcNode* p = G->vertices[i].firstarc->nextarc;
while (p != NULL) {
indegree[p->adjvex]++;
p = p->nextarc;
}
}
return 1;
}
TopologicalOrder(ALGraph* G, SqStack* T,int ve[MAX_VERTEX_NUM])
{
int indegree[MAX_VERTEX_NUM] = { 0 };
FindInDegree(G, indegree);//求入度
SqStack S;
InitStack(&S);
InitStack(T);
int i, count = 0;
for (i = 0; i < G->vexnum; i++) {
if (!indegree[i])
Push(&S, i);
}
while (StackEmpty(&S))
{
Pop(&S, &i);
Push(T, i);
count++;
ArcNode* p = G->vertices[i].firstarc->nextarc;
while (p != NULL) {
int k = p->adjvex;
if (!(--indegree[k]))
Push(&S, k);
if (ve[i] + p->dis > ve[k])
ve[k] = ve[i] + p->dis;
p = p->nextarc;
}
}
if (count < G->vexnum)
return error;
return 1;
}
void dfs(ALGraph *G, int u, char* tmp)
{
char path[100] = { '\0' };
strcpy(path, tmp);
if (G->vertices[u].data == G->vertices[aim].data) {
if (flag)
printf( "或\n");
printf("%s\n",path);
flag = 1;
return;
}
ArcNode* p = G->vertices[u].firstarc->nextarc;
for (; p; p = p->nextarc) {
int k = p->adjvex, dut = p->dis;
if (ve[u] == vl[k] - dut) {
char arr[10] = { '\0' };
arr[0] = u + 1 + '0';
arr[1] = '-';
arr[2] = '>';
arr[3] = k + 1 + '0';
int len = strlen(path);
if (path[len - 5] == arr[0])
{
path[len - 5] = '\0';
}
strcat(path, arr);
strcat(path, ",");
dfs(G, k, path);
}
}
}
CriticalPath(ALGraph* G)
{
SqStack T;
if (TopologicalOrder(G, &T, ve) == -1) { printf("图中有环!\n"); return error; }
int i;
for (i = 0; i < G->vexnum; i++) {
vl[i] = ve[aim];
}
while (StackEmpty(&T))
{
ArcNode* p;
for (Pop(&T, &i), p = G->vertices[i].firstarc->nextarc; p; p = p->nextarc) {
int k = p->adjvex, dut = p->dis;
if (vl[k] - dut < vl[i]) vl[i] = vl[k] - dut;
}
}
printf( "head\t tail\t w\t ve\t vl\t tag\n");
for (int i = 0; i < G->vexnum; i++) {
ArcNode* p;
for (p = G->vertices[i].firstarc->nextarc; p; p = p->nextarc) {
int k = p->adjvex, w = p->dis;
int ve_ = ve[i], vl_ = vl[k] - w;
char tag = (ve_ == vl_) ? '*' : ' ';
printf("%d\t%d\t%d\t%d\t%d\t%c\n",i+1,k+1,w,ve_,vl_,tag);
}
}
printf( "\n关键活动为:\n");
for (int i = 0; i < G->vexnum; i++) {
ArcNode* p = G->vertices[i].firstarc->nextarc;
for (; p; p = p->nextarc) {
int k = p->adjvex, dut = p->dis;
if (ve[i] == vl[k] - dut) {
printf("%d->%d\n", G->vertices[i].data ,G->vertices[k].data );
}
}
}
printf( "\n关键路径为:\n");
dfs(G, 0,"");
printf( "\n花费的时间为至少为:%d\n" ,ve[aim]);
}
int main()
{
ALGraph G;
//freopen("a.txt", "r", stdin);
CreateDN(&G);
CriticalPath(&G);
return 0;
}
测试数据及数据格式:可保存到文档使用重定向输入
9,11
1
2
3
4
5
6
7
8
9
1,2
6
1,3
4
1,4
3
2,5
1
3,5
1
4,6
2
5,7
9
5,8
7
6,8
4
7,9
2
8,9
4
四、心得体会
在拓扑排序的基础上进一步运用了栈、递归、指针、邻接表的操作。也用代码实现了关键路径的计算和选择,收获很多。但在递归这块掌握的还不够熟练,课下会多加练习。Dsf算法有多种写法,我也应尝试多种解决方案来完成。
五、参考文献
[1] 严蔚敏,吴伟民.数据结构(C语言版)[M]. 北京:清华大学出版社,1997.4