zoukankan      html  css  js  c++  java
  • 图->有向无环图->求关键路径

    文字描述

      与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!
    ", 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("
    ");
     96     if(G->kind == DG){
     97         printf("类型:有向图;顶点数 %d, 弧数 %d
    ", G->vexnum, G->arcnum);
     98     }else if(G->kind == DN){
     99         printf("类型:有向网;顶点数 %d, 弧数 %d
    ", G->vexnum, G->arcnum);
    100     }else if(G->kind == UDG){
    101         printf("类型:无向图;顶点数 %d, 弧数 %d
    ", G->vexnum, G->arcnum);
    102     }else if(G->kind == UDN){
    103         printf("类型:无向网;顶点数 %d, 弧数 %d
    ", G->vexnum, G->arcnum);
    104     }
    105     int i = 0;
    106     ArcNode *p = NULL;
    107     printf("邻接表:
    ");
    108     for(i=0; i<G->vexnum; i++){
    109         printf("(%d,%s)	", i,G->vertices[i].data);
    110         p = G->vertices[i].firstarcOUT;
    111         while(p){
    112             if(p->adjvex >= 0)
    113                 printf("(%d,%s) %d	", p->adjvex, G->vertices[p->adjvex].data, p->info);
    114             p = p->nextarc;
    115         }
    116         printf("
    ");
    117     }
    118     printf("逆邻接表:
    ");
    119     for(i=0; i<G->vexnum; i++){
    120         printf("(%d,%s)	", i,G->vertices[i].data);
    121         p = G->vertices[i].firstarcIN;
    122         while(p){
    123             if(p->adjvex >= 0)
    124                 printf("(%d,%s) %d	", p->adjvex, G->vertices[p->adjvex].data, p->info);
    125             p = p->nextarc;
    126         }
    127         printf("
    ");
    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("
    对各个顶点求入度...
    ");
    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
    ", 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("
    进行拓扑排序:");
    194     while(StackEmpty(&S)){
    195         Pop(&S, &j);
    196         //j号顶点入T栈并计数
    197         Push(T, &j);
    198         ++count;
    199         printf("(%d,%s)	", 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("
    ");
    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("
    输出各个顶点的最早发生时间ve和最晚发生时间vl
    ");
    265     for(i=0; i<G->vexnum; i++){
    266         printf("ve(%d,%s)=%d	", i, G->vertices[i].data, ve[i]);
    267         printf("vl(%d,%s)=%d
    ", i, G->vertices[i].data, vl[i]);
    268     }
    269     int ee = 0;
    270     int el = 0;
    271     char tag = 0;
    272     printf("
    输出各活动的最早发生时间ee和最晚发生时间el, *表示该活动为关键路径
    ");
    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
    ", 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("创建有向图时出错!
    ");
    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

  • 相关阅读:
    SQL Server 2008的审核功能
    在SSMS(2008)中对数据库对象进行搜索和筛选
    关于在ASP.NET应用程序中异步调用Web Service的解决方案
    SSIS 2008中的Data Profiler任务
    开始Windows Embedded之旅
    在Access中计算两个日期之间的工作日天数
    当PrintForm遇到"RPC服务不可用的错误”
    REST 的知识 【转载】
    在C#中实现类似Eval这类动态求值的函数【转载】
    行内数据
  • 原文地址:https://www.cnblogs.com/aimmiao/p/10197259.html
Copyright © 2011-2022 走看看