• 数据结构-求关键路径和关键活动


    情境引入:

    在前面我们学过AOV网,AOV网用来确定活动之间的优先顺序。

    在实际应用中,活动之间除了先后顺序外,还有时间上的约束。

    在一个表示工程的带权有向图中,用顶点表示时间,用边表示活动,边上的权值表示活动的持续时

    间,这样的有向图被叫做:“AOE”网。

    通常,AOE网可以用来估测工程的完成时间。

    AOE网中,入度为0的顶点叫做:“源点”,出度为0的顶点叫做:“汇点”。

    为此我们得到AOE网的两条重要性质:

    1.只有在某顶点所代表的事件发生后,从该顶点出发的各活动才能开始。

    2.只有在进入某时间的全部活动结束后,该事件才能开始。

    因此完成工程的最短时间就是从源点到汇点的最短路径长度。具有最长长度的路径被叫作:“关键

    路径”,路径长度是指路径上的各权度之和。关键路径上的活动被叫作关键活动

    而想要缩短工期,则必须对关键活动下手,为此我们可以设计一个算法来实现这个功能。

    代码部分:

    1. #include
    2. #include
    3. #define MAX 100
    4. typedef struct ArcNode{
    5. int adjvex;
    6. int weight;
    7. struct ArcNode *nextarc;
    8. }ArcNode;
    9. typedef struct VNode{
    10. char vertex;
    11. ArcNode *firstarc;
    12. }VNode;
    13. typedef VNode Adjlist[MAX];
    14. typedef struct ALGraph{
    15. Adjlist adjlist;
    16. int vexnum;
    17. int arcnum;
    18. }ALGraph;
    19. int LocateVertex(ALGraph *G,char v) //查询顶点在邻接图中的位置
    20. {
    21. int num;
    22. for(num=0;numvexnum;num++)
    23. if(G->adjlist[num].vertex == v)
    24. return num;
    25. return -1;
    26. }
    27. void CreateALGraph(ALGraph *G) //创建邻接图
    28. {
    29. int i,weight;
    30. int num1,num2;
    31. char v1,v2;
    32. printf("请输入顶点数和边数:\n");
    33. scanf("%d %d",&G->vexnum,&G->arcnum);
    34. getchar();
    35. printf("请输入:{%d}个顶点\n",G->vexnum);
    36. for(i=0;ivexnum;i++){
    37. scanf("%c",&G->adjlist[i].vertex);
    38. G->adjlist[i].firstarc = NULL;
    39. }
    40. getchar();
    41. printf("请输入:{%d}条边,格式->(v1 v2 权值)\n",G->arcnum);
    42. for(i=0;iarcnum;i++){
    43. scanf("%c %c %d",&v1,&v2,&weight);
    44. getchar();
    45. num1 = LocateVertex(G,v1);
    46. num2 = LocateVertex(G,v2);
    47. if(num1 == -1 || num2 == -1){
    48. printf("失败.num1->{%d}、num2->{%d}\n",num1,num2);
    49. i = i - 1;
    50. continue;
    51. }
    52. else{
    53. ArcNode *tmp = (ArcNode*)malloc(sizeof(ArcNode));
    54. tmp->adjvex = num2;
    55. tmp->weight = weight;
    56. tmp->nextarc = G->adjlist[num1].firstarc;
    57. G->adjlist[num1].firstarc = tmp;
    58. printf("成功.\n");
    59. }
    60. }
    61. }
    62. void FindInDegree(ALGraph *G,int *indegree) //查找图中所有顶点入度
    63. {
    64. ArcNode *tmp = NULL;
    65. int i;
    66. for(i=0;ivexnum;i++){
    67. indegree[i] = 0;
    68. }
    69. for(i=0;ivexnum;i++){
    70. tmp = G->adjlist[i].firstarc;
    71. while(tmp){
    72. indegree[tmp->adjvex]++;
    73. tmp = tmp->nextarc;
    74. }
    75. }
    76. }
    77. void CriticalPath(ALGraph *G,int n) //求关键路径函数,n是图中顶点个数
    78. {
    79. ArcNode *tmp = NULL;
    80. int i,v,weight,num,indegree[n];
    81. int ee,el,sum = 0; //ee和el指针
    82. int ve[n],vl[n]; //ve和vl数组
    83. int top1 = 0,top2 = 0; //top1和top2两个栈顶指针
    84. FindInDegree(G,indegree); //top1是正向拓扑排序指针,top2是逆向拓扑排序指针
    85. for(i=0;ivexnum;i++) //最早发生时间ve数组赋初值0
    86. ve[i] = 0;
    87. for(i=0;ivexnum;i++){ //入度为0的顶点入栈(正序栈)
    88. if(indegree[i] == 0){
    89. indegree[i] = top1;
    90. top1 = i + 1;
    91. }
    92. }
    93. while(top1!=0){ //正序栈出栈,同时也是拓扑排序遍历
    94. v = top1 - 1;
    95. top1 = indegree[v]; //出栈
    96. indegree[v] = top2; //构建逆序栈,为逆拓扑排序准备
    97. top2 = v + 1; //逆拓扑栈顶指针加一
    98. tmp = G->adjlist[v].firstarc;
    99. while(tmp){ //进入单个邻接单元的nextarc循环中
    100. weight = tmp->weight;
    101. num = tmp->adjvex;
    102. if(ve[v] + weight > ve[num]){ //永远使ve[i]中所存的是当前消去顶点后的最大值.
    103. ve[num] = ve[v] + weight;
    104. }
    105. if(--indegree[num] == 0){ //减去一个顶点,对应边也要减去,因此邻接边的入度要减少1
    106. indegree[num] = top1; //如果减1后等于0,需要入栈成为一个新的拓扑点
    107. top1 = num + 1;
    108. }
    109. tmp = tmp->nextarc;
    110. }
    111. }
    112. for(i=0;ivexnum;i++){
    113. vl[i] = ve[top2-1]; //vl数组初始化,全部用逆拓扑排序的起点。
    114. }
    115. while(top2!=0){
    116. v = top2 - 1; //出栈,逆拓扑排序
    117. top2 = indegree[v];
    118. tmp = G->adjlist[v].firstarc;
    119. while(tmp){
    120. num = tmp->adjvex;
    121. weight = tmp->weight;
    122. if(vl[num] - weight < vl[v]){ //vl中所存的一定是最小的数值
    123. vl[v] = vl[num] - weight;
    124. }
    125. tmp = tmp->nextarc;
    126. }
    127. }
    128. for(i=0;ivexnum;i++){ //关键路径遍历循环
    129. tmp = G->adjlist[i].firstarc;
    130. while(tmp){
    131. num = tmp->adjvex;
    132. weight = tmp->weight;
    133. ee = ve[i]; //ee==ve
    134. el = vl[num] - weight; //el==vl - weight
    135. if(ee == el){ //如果ee==el那么说明是一个关键路径
    136. printf("边<%c,%c>输出,权值为:{%d}\n",G->adjlist[i].vertex,G->adjlist[num].vertex,weight);
    137. sum += weight;
    138. }
    139. tmp = tmp->nextarc;
    140. }
    141. }
    142. printf("因此关键路径长度为:{%d}\n",sum);
    143. }
    144. int main()
    145. {
    146. ALGraph G;
    147. CreateALGraph(&G);
    148. CriticalPath(&G,G.vexnum);
    149. return 0;
    150. }

    例子:

    上面的例子共ABCDEFGHK九个事件(顶点),共a1->a12(12个活动)。

    算法思想:

    在知道AOE网的两条性质后,我们很容易的想到算法:

    我们先定义下面四个“参量”:

    事件的最早发生时间->“ve”,事件的最迟发生时间->"vl",活动的最早发生时间->"ee",活动的最迟

    发生时间->"el"。

    1.事件的最早发生时间

    ve指的是从源点到顶点V的最长路径长度

    这个长度决定了所有从顶点ve发出的活动能够开工的最早时间。

    因此ve的大小取决于最慢的那个活动,也就是:

    ve[k] = max{ve[j] + dut}

    例如上面的例子,我们可以得到下面的最长路径长度:

    2.事件的最晚发生时间

    事件的最晚发生时间通俗点来说,就是在查找到事件的最早发生时间之后,通过逆序的顺序依次

    减去各边最大值求得。

    公式为:

    vl[j] = min{ve[k] - dut}

    根据上面的的解释,我们可以得到这个例子的vl如下:

    3.活动的最早发生时间

    活动的最早发生时间等于事件的最早发生时间,这是由AOE网的性质决定的,只有当某个事件发生

    了,某个活动才会发生。

    因此我们可以得到:

    4.活动的最晚发生时间

    活动的最晚发生时间是指,在不推迟整个工期的前提下的时间

    因此,有公式:

    el[i] = {vl[k] - dut}

    通过上式,我们可以得到:

    验证部分:

  • 相关阅读:
    第4周学习:MobileNetV1, V2, V3 SENet HybridSN
    pod原理
    hanlp,pyhanlp 实现 NLP 任务
    static关键字
    硬件基本功--MOS管
    英伟达发布世界最大芯片Blackwell GPU:开启AI计算新纪元
    收钱吧研发效能实践之工具篇
    音频pop音的数学与物理解释
    IT行业哪个方向比较好就业?
    操作DAC模块
  • 原文地址:https://blog.csdn.net/zheshiyangyang/article/details/134066074