图->有向无环图->求关键路径
文字描述
与AOV-网相对应的是AOE-网(Activity on Edge)即边表示活动的网。AOE-网是一个带权的有向无环图。其中,顶点表示事件Event,弧表示活动,权表示活动持续的时间。通常,AOE-网可用来估算工程的完成时间。
对AOE-网来说,研究的问题有两个:(1)完成整项工程至少需要多少时间?(2)哪些活动是影响工程进度的关键?
由于在AOE-网中有些活动可以并行地进行,所以完成工程的最短时间是从开始点到完成点的最长路径的长度(指路径上各活动持续时间之和,不是路径上弧的数目)。路径长度最长的路径叫做关键路径。
假设开始点是v1,从v1到vi的最长路径叫事件vi的最早发生时间。这个时间决定了所有以vi为尾的弧所表示的活动的最早开始时间。用e(i)表示活动ai的最早开始时间。用l(i)表示ai的最迟开始时间,这是在不推迟整个工程完成的前提下,活动ai最迟必须开始进行的时间。两者之差l(i)-e(i)表示活动ai的时间余量。我们把l(i)==e(i)的活动叫做关键活动。
显然,关键路径上的所有活动都是关键活动,因此提前完成非关键活动并不能加快工程的进度。
那么如何求得各个活动的最早开始时间e(i)和最晚开始时间l(i)呢?首先应求得事件的最早发生时间ve(j)和最迟发生时间vl(j)。如果活动ai由弧<j,k>表示,其持续时间记为dut(<j,k>),则有如下关系:
e(i) = ve(j)
l(i) = vl(k) – dut(<j,k>)
求ve(j)和vl(j)需分两步进行:
(1) 从ve(0)=0开始向前递推
(2) 从vl(n-1)=ve(n-1)起向后递推
这两个递推公式可以利用之前的拓扑排序算法求得。
示意图
算法分析
算法复杂度同拓扑排序算法,为O(n+e)。
代码实现
1 // 2 // Created by lady on 18-12-29. 3 // 4 5 #include <stdlib.h> 6 #include <stdio.h> 7 #define MAX_VERTEX_NUM 20 //最大顶点数 8 #define MAX_EDGE_NUM 50 //最大弧数 9 typedef enum {DG,DN, UDG, UDN} GraphKind; //{有向图,有向网,无向图,无向网} 10 typedef struct ArcNode{ 11 int adjvex; //该弧所指向的顶点的位置 12 struct ArcNode *nextarc; //指向下一条弧的指针 13 int info; //该弧相关信息的指针 14 }ArcNode; 15 typedef struct VNode{ 16 char data[10];//顶点信息 17 ArcNode *firstarcIN;//第一条以该顶点为弧头的弧结点,其他顶点->该结点 18 ArcNode *firstarcOUT;//第一条以该顶点为弧尾的弧结点,该结点->其他顶点 19 }VNode, AdjList[MAX_VERTEX_NUM]; 20 typedef struct{ 21 AdjList vertices; 22 int vexnum;//图的顶点数 23 int arcnum;//图的弧数 24 int kind; //图的种类标志 25 }ALGraph; 26 27 //根据顶点信息,返回该顶点在图中的位置坐标。 28 int LocateVex(ALGraph *G, char data[]) 29 { 30 int i = 0; 31 for(i=0; i<G->vexnum; i++){ 32 if(!strncmp(G->vertices[i].data, data, strlen(G->vertices[i].data))){ 33 return i; 34 } 35 } 36 return -1; 37 } 38 39 //利用头插法,在弧结点链表头部,插入位置v的弧结点 40 int InsFirst(ArcNode *L, int v, int weight) 41 { 42 if((L==NULL) || (v<0)){ 43 return -1; 44 } 45 ArcNode *n = (ArcNode *)malloc(sizeof(struct ArcNode)); 46 n->adjvex = v; 47 n->nextarc = L->nextarc; 48 n->info = weight; 49 L->nextarc = n; 50 return 0; 51 } 52 53 //采用邻接表存储方法,创建有向网,即带权的有向图 54 int CreateDN(ALGraph *G) 55 { 56 printf("开始创建一个有向图,请输入顶点数,弧数:"); 57 int i = 0, j = 0, k = 0; 58 char v1[10] = {0}, v2[10]={0}, info[10] = {0}; 59 char tmp[20] = {0}; 60 G->kind = DN; 61 scanf("%d,%d", &G->vexnum, &G->arcnum); 62 for(i=0; i<G->vexnum; i++){ 63 printf("输入第%d个顶点: ", i+1); 64 memset(G->vertices[i].data, 0, sizeof(G->vertices[i].data)); 65 scanf("%s", G->vertices[i].data); 66 G->vertices[i].firstarcOUT = (struct ArcNode *)malloc(sizeof(struct ArcNode)); 67 G->vertices[i].firstarcOUT->adjvex = -1; 68 G->vertices[i].firstarcOUT->nextarc = NULL; 69 G->vertices[i].firstarcIN = (struct ArcNode *)malloc(sizeof(struct ArcNode)); 70 G->vertices[i].firstarcIN->adjvex = -1; 71 G->vertices[i].firstarcIN->nextarc = NULL; 72 } 73 for(k=0; k<G->arcnum; k++) 74 { 75 printf("输入第%d条弧(顶点1, 顶点2, 权值): ", k+1); 76 memset(tmp, 0, sizeof(tmp)); 77 scanf("%s", tmp); 78 // sscanf(tmp, "%[^','],%s[^\\n]", v1, v2); 79 sscanf(tmp, "%[^','],%[^','],%s[^\\n]", v1, v2, info); 80 i = LocateVex(G, v1); 81 j = LocateVex(G, v2); 82 if(i<0 || j<0){ 83 printf("<%s,%s> is a invalid arch!\n", v1, v2); 84 return -1; 85 } 86 InsFirst(G->vertices[i].firstarcOUT, j, atoi((const char *)info)); 87 InsFirst(G->vertices[j].firstarcIN, i, atoi((const char *)info)); 88 } 89 return 0; 90 } 91 92 93 void printG(ALGraph *G) 94 { 95 printf("\n"); 96 if(G->kind == DG){ 97 printf("类型:有向图;顶点数 %d, 弧数 %d\n", G->vexnum, G->arcnum); 98 }else if(G->kind == DN){ 99 printf("类型:有向网;顶点数 %d, 弧数 %d\n", G->vexnum, G->arcnum); 100 }else if(G->kind == UDG){ 101 printf("类型:无向图;顶点数 %d, 弧数 %d\n", G->vexnum, G->arcnum); 102 }else if(G->kind == UDN){ 103 printf("类型:无向网;顶点数 %d, 弧数 %d\n", G->vexnum, G->arcnum); 104 } 105 int i = 0; 106 ArcNode *p = NULL; 107 printf("邻接表:\n"); 108 for(i=0; i<G->vexnum; i++){ 109 printf("(%d,%s)\t", i,G->vertices[i].data); 110 p = G->vertices[i].firstarcOUT; 111 while(p){ 112 if(p->adjvex >= 0) 113 printf("(%d,%s) %d\t", p->adjvex, G->vertices[p->adjvex].data, p->info); 114 p = p->nextarc; 115 } 116 printf("\n"); 117 } 118 printf("逆邻接表:\n"); 119 for(i=0; i<G->vexnum; i++){ 120 printf("(%d,%s)\t", i,G->vertices[i].data); 121 p = G->vertices[i].firstarcIN; 122 while(p){ 123 if(p->adjvex >= 0) 124 printf("(%d,%s) %d\t", p->adjvex, G->vertices[p->adjvex].data, p->info); 125 p = p->nextarc; 126 } 127 printf("\n"); 128 } 129 return; 130 } 131 132 133 #define STACK_INIT_SIZE 20 //栈的初始分配量大小 134 #define STACK_INCREMENT 5 //栈容量不足时需新增的容量大小 135 typedef struct { 136 int *base; //指向栈底指针 137 int *top; //指向栈顶指针 138 int stacksize; //栈的当前容量大小 139 }SqStack; 140 int InitStack(SqStack *s); //初始化一个栈 141 int StackEmpty(SqStack *s); //判断栈是否为空 142 int Push(SqStack *S, int *e); //入栈函数 143 int Pop(SqStack *S, int *e); //出栈函数 144 145 //算法各个顶点的入度,并将结果存放在indegree数组中 146 int FindInDegree(ALGraph *G, int indegree[]) 147 { 148 printf("\n对各个顶点求入度...\n"); 149 int i = 0; 150 ArcNode *p = NULL; 151 for(i=0; i<G->vexnum; i++) { 152 p = G->vertices[i].firstarcIN; 153 while (p) { 154 if (p->adjvex >= 0) { 155 indegree[i] += 1; 156 } 157 p = p->nextarc; 158 } 159 } 160 for(i=0; i<G->vexnum; i++){ 161 printf("(%d,%s)的入度为%d\n", i, G->vertices[i].data, indegree[i]); 162 } 163 return 0; 164 } 165 int ve[MAX_EDGE_NUM] = {0}; 166 int vl[MAX_EDGE_NUM] = {0}; 167 168 int ToplogicalSort(ALGraph *G, SqStack *T) 169 { 170 int i = 0; 171 int j = 0; 172 int k = 0; 173 int count = 0; 174 int indegree[MAX_VERTEX_NUM] = {0}; 175 ArcNode *p = NULL; 176 SqStack S; 177 //求各个顶点的入度 178 FindInDegree(G, indegree); 179 //初始化栈S,保存零入度顶点栈 180 InitStack(&S); 181 //将入度为0的顶点入栈S. 182 for(i=0; i<G->vexnum; i++){ 183 if(!indegree[i]) { 184 Push(&S, &i); 185 } 186 } 187 //初始化栈T,为拓扑序列顶点栈 188 InitStack(T); 189 //初始化 190 for(i=0; i<G->vexnum; i++){ 191 ve[i] = 0; 192 } 193 printf("\n进行拓扑排序:"); 194 while(StackEmpty(&S)){ 195 Pop(&S, &j); 196 //j号顶点入T栈并计数 197 Push(T, &j); 198 ++count; 199 printf("(%d,%s)\t", j, G->vertices[j].data); 200 //对j号顶点的每个邻接点的入度减1 201 for(p=G->vertices[j].firstarcOUT; p; p=p->nextarc){ 202 k = p->adjvex; 203 if(k<0){ 204 continue; 205 } 206 //若入度为0,则入栈S 207 if(!(--indegree[k])){ 208 Push(&S, &k); 209 } 210 if(ve[j]+p->info > ve[k]) 211 ve[k] = ve[j]+p->info; 212 } 213 } 214 printf("\n"); 215 if(count<G->vexnum){ 216 //该有向网有环 217 return -1; 218 }else{ 219 return 0; 220 } 221 } 222 223 //G为有向图, 输出G的各项关键活动 224 int CriticalPath(ALGraph *G) 225 { 226 SqStack T; 227 if(ToplogicalSort(G, &T)<0){ 228 return -1; 229 } 230 int i = 0; 231 int j = 0; 232 int k = 0; 233 int dut = 0; 234 ArcNode *p = NULL; 235 //初始化顶点时间的最迟发生时间 236 for(i=0; i<G->vexnum; i++){ 237 vl[i] = ve[i]; 238 } 239 //按照拓扑逆序求各顶点的vl值 240 while(StackEmpty(&T)){ 241 Pop(&T, &j); 242 243 for(p=G->vertices[j].firstarcOUT; p; p=p->nextarc){ 244 k = p->adjvex; 245 if(k<0) 246 continue; 247 dut = p->info; //dut(<j,k>) 248 if(vl[k]-dut < vl[j]) 249 vl[j] = vl[k] - dut; 250 } 251 252 253 for(p=G->vertices[j].firstarcIN; p; p=p->nextarc) { 254 k = p->adjvex; 255 if (k < 0) 256 continue; 257 dut = p->info; //dut<k,j> 258 259 if (vl[j] - dut > vl[k]) { 260 vl[k] = vl[j] - dut; 261 } 262 } 263 } 264 printf("\n输出各个顶点的最早发生时间ve和最晚发生时间vl\n"); 265 for(i=0; i<G->vexnum; i++){ 266 printf("ve(%d,%s)=%d\t", i, G->vertices[i].data, ve[i]); 267 printf("vl(%d,%s)=%d\n", i, G->vertices[i].data, vl[i]); 268 } 269 int ee = 0; 270 int el = 0; 271 char tag = 0; 272 printf("\n输出各活动的最早发生时间ee和最晚发生时间el, *表示该活动为关键路径\n"); 273 for(j=0; j<G->vexnum; j++){ 274 for(p=G->vertices[j].firstarcOUT; p; p=p->nextarc){ 275 k = p->adjvex; 276 if(k<0){ 277 continue; 278 } 279 dut = p->info; 280 ee = ve[j]; 281 el = vl[k]-dut; 282 tag = (ee==el)?'*':' '; 283 //输出关键活动 284 printf("(%d,%s)->(%d,%s), weight:%d, ee=%d, el=%d, tag=%c\n", j, G->vertices[j].data, k, G->vertices[k].data, dut, ee, el, tag); 285 } 286 } 287 return 0; 288 } 289 290 int main(int argc, char *argv[]) 291 { 292 ALGraph G; 293 //创建有向图 294 if(CreateDN(&G)<0){ 295 printf("创建有向图时出错!\n"); 296 return -1; 297 } 298 //打印图 299 printG(&G); 300 //求关键路径 301 CriticalPath(&G); 302 return 0; 303 } 304 305 306 int InitStack(SqStack *S){ 307 S->base = (int *) malloc(STACK_INIT_SIZE * sizeof(int)); 308 if(!S->base){ 309 return -1; 310 } 311 S->top = S->base; 312 S->stacksize = STACK_INIT_SIZE; 313 return 0; 314 } 315 316 int StackEmpty(SqStack *s){ 317 if(s->base == s->top){ 318 return 0; 319 }else{ 320 return -1; 321 } 322 } 323 324 int Push(SqStack *s, int *e){ 325 if((s->top-s->base) >= s->stacksize){ 326 s->base = (int*)realloc(s->base, (s->stacksize+STACK_INCREMENT)*(sizeof(int))); 327 if(!s->base){ 328 return -1; 329 } 330 s->top = s->base + s->stacksize; 331 s->stacksize += STACK_INCREMENT; 332 } 333 if(e == NULL){ 334 return -1; 335 }else{ 336 *s->top = *e; 337 } 338 s->top += 1; 339 return 0; 340 } 341 342 int Pop(SqStack *s, int *e) 343 { 344 if(s->top == s->base) { 345 return -1; 346 }else{ 347 s->top -=1; 348 *e = *s->top; 349 return 0; 350 } 351 }
代码运行
/home/lady/CLionProjects/untitled/cmake-build-debug/untitled
开始创建一个有向图,请输入顶点数,弧数:9,11
输入第1个顶点: V1
输入第2个顶点: V2
输入第3个顶点: V3
输入第4个顶点: V4
输入第5个顶点: V5
输入第6个顶点: V6
输入第7个顶点: V7
输入第8个顶点: V8
输入第9个顶点: V9
输入第1条弧(顶点1, 顶点2, 权值): V1,V2,6
输入第2条弧(顶点1, 顶点2, 权值): V1,V3,4
输入第3条弧(顶点1, 顶点2, 权值): V1,V4,5
输入第4条弧(顶点1, 顶点2, 权值): V2,V5,1
输入第5条弧(顶点1, 顶点2, 权值): V3,V5,1
输入第6条弧(顶点1, 顶点2, 权值): V4,V6,2
输入第7条弧(顶点1, 顶点2, 权值): V5,V7,9
输入第8条弧(顶点1, 顶点2, 权值): V5,V8,7
输入第9条弧(顶点1, 顶点2, 权值): V6,V8,4
输入第10条弧(顶点1, 顶点2, 权值): V7,V9,2
输入第11条弧(顶点1, 顶点2, 权值): V8,V9,4
类型:有向网;顶点数 9, 弧数 11
邻接表:
(0,V1) (3,V4) 5 (2,V3) 4 (1,V2) 6
(1,V2) (4,V5) 1
(2,V3) (4,V5) 1
(3,V4) (5,V6) 2
(4,V5) (7,V8) 7 (6,V7) 9
(5,V6) (7,V8) 4
(6,V7) (8,V9) 2
(7,V8) (8,V9) 4
(8,V9)
逆邻接表:
(0,V1)
(1,V2) (0,V1) 6
(2,V3) (0,V1) 4
(3,V4) (0,V1) 5
(4,V5) (2,V3) 1 (1,V2) 1
(5,V6) (3,V4) 2
(6,V7) (4,V5) 9
(7,V8) (5,V6) 4 (4,V5) 7
(8,V9) (7,V8) 4 (6,V7) 2
对各个顶点求入度...
(0,V1)的入度为0
(1,V2)的入度为1
(2,V3)的入度为1
(3,V4)的入度为1
(4,V5)的入度为2
(5,V6)的入度为1
(6,V7)的入度为1
(7,V8)的入度为2
(8,V9)的入度为2
进行拓扑排序:(0,V1) (1,V2) (2,V3) (4,V5) (6,V7) (3,V4) (5,V6) (7,V8) (8,V9)
输出各个顶点的最早发生时间ve和最晚发生时间vl
ve(0,V1)=0 vl(0,V1)=0
ve(1,V2)=6 vl(1,V2)=6
ve(2,V3)=4 vl(2,V3)=6
ve(3,V4)=5 vl(3,V4)=8
ve(4,V5)=7 vl(4,V5)=7
ve(5,V6)=7 vl(5,V6)=10
ve(6,V7)=16 vl(6,V7)=16
ve(7,V8)=14 vl(7,V8)=14
ve(8,V9)=18 vl(8,V9)=18
输出各活动的最早发生时间ee和最晚发生时间el, *表示该活动为关键路径
(0,V1)->(3,V4), weight:5, ee=0, el=3, tag=
(0,V1)->(2,V3), weight:4, ee=0, el=2, tag=
(0,V1)->(1,V2), weight:6, ee=0, el=0, tag=*
(1,V2)->(4,V5), weight:1, ee=6, el=6, tag=*
(2,V3)->(4,V5), weight:1, ee=4, el=6, tag=
(3,V4)->(5,V6), weight:2, ee=5, el=8, tag=
(4,V5)->(7,V8), weight:7, ee=7, el=7, tag=*
(4,V5)->(6,V7), weight:9, ee=7, el=7, tag=*
(5,V6)->(7,V8), weight:4, ee=7, el=10, tag=
(6,V7)->(8,V9), weight:2, ee=16, el=16, tag=*
(7,V8)->(8,V9), weight:4, ee=14, el=14, tag=*
Process finished with exit code 0
/home/lady/CLionProjects/untitled/cmake-build-debug/untitled
开始创建一个有向图,请输入顶点数,弧数:6,8
输入第1个顶点: V1
输入第2个顶点: V2
输入第3个顶点: V3
输入第4个顶点: V4
输入第5个顶点: V5
输入第6个顶点: V6
输入第1条弧(顶点1, 顶点2, 权值): V1,V2,3
输入第2条弧(顶点1, 顶点2, 权值): V1,V3,2
输入第3条弧(顶点1, 顶点2, 权值): V2,V4,2
输入第4条弧(顶点1, 顶点2, 权值): V2,V5,3
输入第5条弧(顶点1, 顶点2, 权值): V3,V4,4
输入第6条弧(顶点1, 顶点2, 权值): V3,V6,3
输入第7条弧(顶点1, 顶点2, 权值): V4,V6,2
输入第8条弧(顶点1, 顶点2, 权值): V5,V6,1
类型:有向网;顶点数 6, 弧数 8
邻接表:
(0,V1) (2,V3) 2 (1,V2) 3
(1,V2) (4,V5) 3 (3,V4) 2
(2,V3) (5,V6) 3 (3,V4) 4
(3,V4) (5,V6) 2
(4,V5) (5,V6) 1
(5,V6)
逆邻接表:
(0,V1)
(1,V2) (0,V1) 3
(2,V3) (0,V1) 2
(3,V4) (2,V3) 4 (1,V2) 2
(4,V5) (1,V2) 3
(5,V6) (4,V5) 1 (3,V4) 2 (2,V3) 3
对各个顶点求入度...
(0,V1)的入度为0
(1,V2)的入度为1
(2,V3)的入度为1
(3,V4)的入度为2
(4,V5)的入度为1
(5,V6)的入度为3
进行拓扑排序:(0,V1) (1,V2) (4,V5) (2,V3) (3,V4) (5,V6)
输出各个顶点的最早发生时间ve和最晚发生时间vl
ve(0,V1)=0 vl(0,V1)=0
ve(1,V2)=3 vl(1,V2)=4
ve(2,V3)=2 vl(2,V3)=2
ve(3,V4)=6 vl(3,V4)=6
ve(4,V5)=6 vl(4,V5)=7
ve(5,V6)=8 vl(5,V6)=8
输出各活动的最早发生时间ee和最晚发生时间el, *表示该活动为关键路径
(0,V1)->(2,V3), weight:2, ee=0, el=0, tag=*
(0,V1)->(1,V2), weight:3, ee=0, el=1, tag=
(1,V2)->(4,V5), weight:3, ee=3, el=4, tag=
(1,V2)->(3,V4), weight:2, ee=3, el=4, tag=
(2,V3)->(5,V6), weight:3, ee=2, el=5, tag=
(2,V3)->(3,V4), weight:4, ee=2, el=2, tag=*
(3,V4)->(5,V6), weight:2, ee=6, el=6, tag=*
(4,V5)->(5,V6), weight:1, ee=6, el=7, tag=
Process finished with exit code 0
posted on 2018-12-29 17:38 LiveWithACat 阅读(976) 评论(0) 编辑 收藏 举报