【图的应用四:关键路径】- 用 C 语言实现关键路径

目录

一、AOE-网

二、算法的实现

2.1 - ALGraph.h

2.2 - ALGraph.c

2.3 - Test.c


 


一、AOE-网

与 AOV-网相对应的是 AOE-网(Activity On Edge),即以边表示活动的网。AOE-网是一个带权的有向无环图,其中,顶点表示事件、弧表示活动,权表示活动持续的时间。通常,AOE-网可用来估算工程的完成时间

例如,下图所示为一个有 11 项活动的 AOE-网。其中有 9 个事件 v0, v1, ..., v8,每个事件表示在它之前的活动已经完成,在它之后的活动可以开始。例如,v0 表示整个工程开始,v8 表示整个过程结束,v4 表示 a4 和 a5 已经完成,a7 和 a8 可以开始了。与每个活动相联系的数是执行该活动所需的时间,比如,活动 a1 需要 6 天,a2 需要 4 天等。

AOE-网在工程计划和经营管理中有广泛的应用,针对实际的应用问题,通常需要解决以下两个问题:

  1. 估算完成整项工程至少需要多少时间。

  2. 判定哪些活动是影响工程进度的关键。

工程进度控制的关键在于抓住关键活动。在一定范围内,非关键活动的提前完成对于整个工程的进度没有直接的好处,它的稍许拖延也不会影响整个工程的进度。工程的指挥者可以把非关键活动的人力和物力资源暂时调给关键活动,加速其进展,以使整个工程提前完工。

由于整个工程只有一个开始点和一个完成点,故在正常的情况(无环)下,网中只有一个入度为零的点,称作源点,也只有一个出度为零的点,称作汇点。在 AOE-网中,一条路径各弧上的权值之和称为该路径的带权路径长度(后面简称路径长度)。要估算整项工程完成的最短时间,就是要找一条从源点到汇点的带权路径长度最长的路径,称为关键路径。关键路径上的活动叫做关键活动,这些活动是影响工程进度的关键,它们的提前或拖延使整个工程提前或拖延。

例如,在上图中,v0 是源点,v8 是汇点,关键路径有两条:(v0, v1, v4, v6, v8) 和 (v0, v1, v4, v7, v8),长度均为 18,关键活动为 (a1, a4, a7, a10) 和 (a1, a4, a8, a11)。比如,关键活动 a1 需要 6 天完成,如果 a1 提前 1 天,整个工程也可以提前 1 天完成。所以不论估算工期,还是研究如何加快工程进度,主要问题就在于要找到 AOE-网的关键路径。

如何确定关键路径,首先定义 4 个描述量。

  1. 事件 vi 的最早发生时间 ve(i)

    进入事件 vi 的每一活动都结束,vi 才可发生,所以 ve(i) 是从源点到 vi 的最长路径长度。

    求 ve(i) 的值,可根据拓扑顺序从源点开始向汇点递推。通常将工程的开始顶点事件 v0 的最早发生时间定义为 0,即:ve(0) = 0 \\ ve(i) = Max\{ ve(k) + w_{k,i} \} <v_k, v_i> \in T, 1 \le i \le n-1

    其中,T 是所有以 vi 为头的弧的集合,w_{k, i} 是弧 <vk, vi> 的权值,即对应活动 <vk, vi> 的持续时间。

  2. 事件 vi 的最迟发生事件 vl(i)

    事件 vi 的发生不得延误 vi 的每一后继事件的最迟发生事件。为了不拖延工期,vi 的最迟发生时间不得迟于其后继事件 vk 的最迟发生事件减去活动 <vi, vk> 的持续事件。

    求出 ve(i) 后,可根据逆拓扑顺序从汇点开始向源点递推,求出 vl(i)。vl(n-1) = ve(n-1) \\ vl(i) = Min\{ vl(k) - w_{i, k} \} <v_i, v_k> \in S, 0 \le i \le n-2

    其中,S 是所有以 vi 为为的弧,w_{i, k} 是弧 <vi, vk> 的权值。

  3. 活动 ai = <vj, vk> 的最早开始时间 ae(i)

    只有事件 vj 发生了,活动 ai 才能开始,所以活动 ai 的最早开始时间等于事件 vj 的最早发生时间 ve(j),即:ae(i) = ve(j)

  4. 活动 ai = <vj, vk> 的最晚开始时间 al(i)

    活动 ai 的开始时间需保证不延误事件 vk 的最迟发生时间。所以活动 ai 的最晚开始时间 al(i) 等于事件 vk 的最迟发生时间 vl(k) 减去活动 ai 的持续时间 w_{j, k},即:al(i) = vl(k) - w_{j, k}

显然,对于关键活动而言,ae(i) = al(i)。对于非关键活动,al(i) - ae(i) 的值是该工程的期限余量,在此范围内的适度延误不会影响整个工程的工期


二、算法的实现

由于每个事件的最早发生时间 ve(i) 和最迟发生时间 vl(i) 要在拓扑序列的基础上进行计算,所以关键路径算法的实现要基于拓扑排序算法,我们仍采用邻接表做有向图的存储结构

2.1 - ALGraph.h

#pragma once#include <stdbool.h>typedef int ArcType;
typedef char VertexType;#define DEFAULT_CAPACITY 2typedef struct ArcNode
{int adjVexPos;struct ArcNode* nextArc;ArcType weight;
}ArcNode;typedef struct VertexNode
{VertexType vertex;ArcNode* firstArc;
}VertexNode;typedef struct ALGraph
{VertexNode* vertices;int vSize;int aSize;int capacity;
}ALGraph;void ALGraphInit(ALGraph* pg);void ShowAdjList(ALGraph* pg);int GetVertexPos(ALGraph* pg, VertexType v);void InsertVertex(ALGraph* pg, VertexType v);
void InsertArc(ALGraph* pg, VertexType v1, VertexType v2, ArcType weight);void ALGraphDestroy(ALGraph* pg);// 拓扑排序
bool TopologicalSort(ALGraph* pg, int* topo);// 关键路径
void CriticalPath(ALGraph* pg);

2.2 - ALGraph.c

#include "ALGraph.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include "Stack.h"void ALGraphInit(ALGraph* pg)
{assert(pg);pg->vSize = pg->aSize = 0;pg->capacity = DEFAULT_CAPACITY;pg->vertices = (VertexNode*)malloc(sizeof(VertexNode) * pg->capacity);assert(pg->vertices);for (int i = 0; i < pg->capacity; ++i){pg->vertices[i].firstArc = NULL;}
}void ShowAdjList(ALGraph* pg)
{assert(pg);for (int i = 0; i < pg->vSize; ++i){printf("%d %c:>", i, pg->vertices[i].vertex);ArcNode* cur = pg->vertices[i].firstArc;while (cur){printf("%d-->", cur->adjVexPos);cur = cur->nextArc;}printf("NULL\n");}
}int GetVertexPos(ALGraph* pg, VertexType v)
{assert(pg);for (int i = 0; i < pg->vSize; ++i){if (pg->vertices[i].vertex == v)return i;}return -1;
}void InsertVertex(ALGraph* pg, VertexType v)
{assert(pg);if (pg->vSize == pg->capacity){VertexNode* tmp = (VertexNode*)realloc(pg->vertices, sizeof(VertexNode) * 2 * pg->capacity);assert(tmp);pg->vertices = tmp;for (int i = pg->capacity; i < 2 * pg->capacity; ++i){pg->vertices[i].firstArc = NULL;}pg->capacity *= 2;}pg->vertices[pg->vSize++].vertex = v;
}void InsertArc(ALGraph* pg, VertexType v1, VertexType v2, ArcType weight)
{assert(pg);int pos1 = GetVertexPos(pg, v1);int pos2 = GetVertexPos(pg, v2);if (pos1 == -1 || pos2 == -1)return;// 插入 <v1, v2>ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));assert(p);p->adjVexPos = pos2;p->weight = weight;// 头插p->nextArc = pg->vertices[pos1].firstArc;pg->vertices[pos1].firstArc = p;++pg->aSize;
}void ALGraphDestroy(ALGraph* pg)
{assert(pg);for (int i = 0; i < pg->vSize; ++i){ArcNode* cur = pg->vertices[i].firstArc;while (cur){// 头删pg->vertices[i].firstArc = cur->nextArc;free(cur);cur = pg->vertices[i].firstArc;}}free(pg->vertices);pg->vertices = NULL;pg->vSize = pg->aSize = pg->capacity = 0;
}// 拓扑排序
bool TopologicalSort(ALGraph* pg, int* topo)
{assert(pg);int* indegree = (int*)malloc(sizeof(int) * pg->vSize);assert(indegree);for (int i = 0; i < pg->vSize; ++i){indegree[i] = 0;}for (int i = 0; i < pg->vSize; ++i){ArcNode* cur = pg->vertices[i].firstArc;while (cur){++indegree[cur->adjVexPos];cur = cur->nextArc;}}Stack st;StackInit(&st);for (int i = 0; i < pg->vSize; ++i){if (indegree[i] == 0)StackPush(&st, i);}int cnt = 0;while (!StackEmpty(&st)){int i = StackTop(&st);StackPop(&st);topo[cnt++] = i;ArcNode* cur = pg->vertices[i].firstArc;while (cur){if (--indegree[cur->adjVexPos] == 0)StackPush(&st, cur->adjVexPos);cur = cur->nextArc;}}StackDestroy(&st);if (cnt == pg->vSize)return true;elsereturn false;
}// 关键路径
void CriticalPath(ALGraph* pg)
{assert(pg);int* topo = (int*)malloc(sizeof(int) * pg->vSize);assert(topo);if (!TopologicalSort(pg, topo)){printf("网中存在有向环!\n");free(topo);return;}int* ve = (int*)malloc(sizeof(int) * pg->vSize);int* vl = (int*)malloc(sizeof(int) * pg->vSize);assert(ve && vl);for (int i = 0; i < pg->vSize; ++i){ve[i] = 0;}// 按照拓扑顺序求每个事件的最早发生时间for (int i = 0; i < pg->vSize - 1; ++i){int k = topo[i];ArcNode* cur = pg->vertices[k].firstArc;while (cur){if (ve[k] + cur->weight > ve[cur->adjVexPos])ve[cur->adjVexPos] = ve[k] + cur->weight;cur = cur->nextArc;}}for (int i = 0; i < pg->vSize; ++i){vl[i] = ve[topo[pg->vSize - 1]];}// 按照逆拓扑顺序求每个事件的最迟发生时间for (int i = pg->vSize - 2; i >= 0; --i){int k = topo[i];ArcNode* cur = pg->vertices[k].firstArc;while (cur){if (vl[cur->adjVexPos] - cur->weight < vl[k])vl[k] = vl[cur->adjVexPos] - cur->weight;cur = cur->nextArc;}}// 判定每一活动是否为关键活动for (int i = 0; i < pg->vSize; ++i){ArcNode* cur = pg->vertices[i].firstArc;while (cur){int ae = ve[i];int al = vl[cur->adjVexPos] - cur->weight;if (ae == al)printf("<%c, %c> 是关键活动\n", pg->vertices[i].vertex, pg->vertices[cur->adjVexPos].vertex);cur = cur->nextArc;}}free(topo);free(ve);free(vl);
}

2.3 - Test.c

#include "ALGraph.h"
#include <stdio.h>int main()
{ALGraph g;ALGraphInit(&g);InsertVertex(&g, 'A');InsertVertex(&g, 'B');InsertVertex(&g, 'C');InsertVertex(&g, 'D');InsertVertex(&g, 'E');InsertVertex(&g, 'F');InsertVertex(&g, 'G');InsertVertex(&g, 'H');InsertVertex(&g, 'I');InsertArc(&g, 'A', 'B', 6);InsertArc(&g, 'A', 'C', 4);InsertArc(&g, 'A', 'D', 5);InsertArc(&g, 'B', 'E', 1);InsertArc(&g, 'C', 'E', 1);InsertArc(&g, 'D', 'F', 2);InsertArc(&g, 'E', 'G', 9);InsertArc(&g, 'E', 'H', 7);InsertArc(&g, 'F', 'H', 4);InsertArc(&g, 'G', 'I', 2);InsertArc(&g, 'H', 'I', 4);ShowAdjList(&g);printf("\n");CriticalPath(&g);ALGraphDestroy(&g);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/297629.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git 使用方法自用(勿进)本地开发分支推上线上开发分支

一、//查看状态 1.git status 二、//查看改了哪个文件夹 1.git diff 2.//会出现改了哪个文件夹src/components/partials/Slider.js 三、//查看改了的文件夹里面具体改了啥内容 1.git diff src/components/partials/Slider.js 四、提交所有 1. git add . 五、写备注…

攻防世界——game 游戏

下载下来是一个exe文件&#xff0c;可以用IDA打开 我们先运行一下 这是属于第二种类型&#xff0c;完成一个操作后给你flag 这种题我更倾向于动调直接得到flag 我们查壳 没有保护壳&#xff0c;直接32打开 进入字符串界面&#xff0c;找到显示的那部分 int __cdecl main_0(…

ardupilot开发 --- 风机不停机巡检 篇

在哪里创建的siyi实例&#xff1f; 如何传递飞控的时间戳给siyi相机&#xff1f; AP_RTC_ENABLED在waf编译时配置为1&#xff1f;&#xff1f; 如何配置&#xff1f; 在lua脚本中如何获取这个时间AP::rtc().get_utc_usec(utc_usec)&#xff1f;&#xff1f;&#xff1f; inclu…

mac传输文件到windows

前言 由于mac系统与windows系统文件格式不同&#xff0c;通过U盘进行文件拷贝时&#xff0c;导致无法拷贝。官方解决方案如下&#xff0c;但是描述的比较模糊。看我的操作步骤即可。 https://support.apple.com/zh-cn/guide/mac-help/mchlp1657/12.0/mac/12.6 前提条件 mac与…

C语言沉浸式刷题【C语言必刷题】

1.猜凶手 某地发生了一起谋杀案&#xff0c;警察通过排查确定杀人凶手必为四个嫌疑犯的一个&#xff0c;以下是4个嫌犯的供词。已知&#xff08;请编写代码找出凶手&#xff09; A说&#xff1a;不是我。 B说&#xff1a;是C。C说&#xff1a;是D。D说&#xff1a;C再胡说。 程…

前端工程注入版本号

文章目录 一、前言二、webpack三、vite四、最后 一、前言 容器化时代&#xff0c;当页面出现问题时&#xff0c;如果你的新版本有可能已经修复了&#xff0c;那样你再排查它就没有意义了。为什么不一定是最新版本呢&#xff1f;一是可能是缓存作祟&#xff0c;二是可能运维成员…

一起玩儿物联网人工智能小车(ESP32)——14. 用ESP32的GPIO控制智能小车运动起来(二)

摘要&#xff1a;本文主要讲解如何使用Mixly实现对单一车轮的运动控制。 下面就该用程序控制我们的小车轮子转起来了。打开Mixly软件&#xff0c;然后单击顶部“文件”菜单中的“新建”功能&#xff0c;我们来开启一个新程序的开发工作。 我们的工作同样是先从最简单的开始&am…

Flink 客户端操作命令及可视化工具

Flink提供了丰富的客户端操作来提交任务和与任务进行交互。下面主要从Flink命令行、Scala Shell、SQL Client、Restful API和 Web五个方面进行整理。 在Flink安装目录的bin目录下可以看到flink&#xff0c;start-scala-shell.sh和sql-client.sh等文件&#xff0c;这些都是客户…

【隐私保护】Presidio简化了PII匿名化

自我介绍 做一个简单介绍&#xff0c;酒架年近48 &#xff0c;有20多年IT工作经历&#xff0c;目前在一家500强做企业架构&#xff0e;因为工作需要&#xff0c;另外也因为兴趣涉猎比较广&#xff0c;为了自己学习建立了三个博客&#xff0c;分别是【全球IT瞭望】&#xff0c;【…

外贸多语言电商系统的运作流程

外贸多语言电商系统的运作流程通常包括以下几个步骤&#xff1a; 1. 网站搭建和设计&#xff1a;首先需要搭建一个多语言电商网站&#xff0c;可以选择现有的电商平台或自行开发。网站设计应考虑不同语言和文化背景的用户需求&#xff0c;包括界面布局、导航结构、语言切换等。…

重要通知,事关校营宝新老用户,敬请知悉!

尊敬的校营宝用户&#xff0c;感谢您选择校营宝培训学校管理系统&#xff0c;我们的发展离不开您的支持鼓励&#xff0c;只要您选择校营宝&#xff0c;就是我们的终身客户&#xff0c;我们承诺给您终身的售后咨询服务&#xff01; 校营宝培训学校管理系统是面向校外艺术类培训…

Java之Synchronized与锁升级

Synchronized与锁升级 一、概述 在多线程并发编程中 synchronized 一直是元老级角色&#xff0c;很多人都会称呼它为重量级锁。但是&#xff0c;随着 Java SE 1.6 对 synchronized 进行了各种优化之后&#xff0c;有些情况下它就并不那么重了。 本文详细介绍 Java SE 1.6 中为…