图论--关键路径
来源:互联网 发布:怎么用mac玩美服lol 编辑:程序博客网 时间:2024/06/09 18:20
拓扑排序主要是为解决一个工程能否顺序进行的问题,但有时我们还需要解决工程完成需要的最短时间问题。如果我们要对一个流程图获得最短时间,就必须要分析它们的拓扑关系,并且找到当中最关键的流程,这个流程的时间就是最短时间。
在前面讲了AOV网的基础上,来介绍一个新的概念。在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种有向图的边表示活动的网,称之为AOE网(Activity On edge Network)。由于一个工程,总有一个开始,一个结束,在正常情况下,AOE网只有一个源点一个汇点。
既然AOE网是表示工程流程的,所以就具有明显的工程属性。只有在某顶点代表的事件发生后,从该顶点出发的各活动才能开始。只有在进入某顶点的各活动都已经结束,该顶点代表的事件才能发生。
尽管AOV网和AOE网都是用来对工程建模的,但它们还是有很大的区别,主要体现在AOV网是顶点表示活动的网,它只描述活动之间的制约关系,而AOE网是用边表示活动的网,边上的权值表示活动持续的时间,如图7-9-3所示两图的对比。因此,AOE网是要建立在活动之间制约关系没有矛盾的基础之上,再来分析完成整个工程需要多少时间,或者为缩短完成工程所需时间,应当加快哪些活动等问题。
我们把路径上各个活动所持续的时间之后称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上完成的活动叫关键活动。显然就图7-9-3的AOE网而言,开始->发动机完成->部件集中到位->组装完成就是关键路径,路径长度为5.5。
如果我们需要缩短整个工期,去改进轮子的生产效率,哪怕改动成0.1也无益于整个工期的变化,只有缩短关键路径上的关键活动时间才才可以减少整个工期长度。例如如果发动机制造缩短为2.5,整车组装缩短为1.5,那么关键路径就为4.5,整整缩短了一天的时间。
如果某项活动的最早开始时间和最晚开始时间一样,表示中间没有空隙,则此项活动就为关键活动。为此,我们需要定义以下几个参数。
1、事件的最早发生时间 etv(earliest time of vertex):即顶点vk 的最早发生时间。
2、事件的最晚发生时间 ltv(latest time of vertex):即顶点vk 的最短发生时间。也就是每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个工期。
3、活动的最早开工时间 ete (earliest time of edge):即弧ak 的最早发生时间。
4、活动的最晚开工时间 lte (latest time of edge ):即弧ak 的最晚发生时间,也就是不推迟工期的最晚开工时间。
我们首先求得1和2,而 ete 本来是表示活动<vk, vj> 的最早开工时间,是针对弧来说的,但只有此弧的弧尾顶点vk的事件发生了,它才可以开始,因此ete = etv[k]。
而lte 表示的是活动<vk, vj> 的最晚开工时间,但此活动再晚也不能等vj 事件发生才开始,所以lte = ltv[j] - len<vk, vj> 。
最终,我们再来判断ete 和 lte 是否相等,相等意味着活动没有任何空闲,是关键路径,否则就不是。
现在来谈谈如何求etv 和 ltv,以下图为例:假设我们现在已经求得顶点v0对应的 etv[0] = 0,顶点v1对应的etv[1] = 3, 顶点v2对应的etv[2] = 4, 现在我们需要求顶点v3对应的etv[3],要开始v3那么前面耗时最长的工程应该刚好完成,其实就是求etv[1] + len<v1, v3> 与 etv[2] + len<v2, v3> 的较大值。显然 3+5 < 4+8, 得到etv[3] = 12, 如图所示。
由此我们也可以得出计算顶点vk的最早发生时间即求etv[k]的公式是:
求事件的最早发生时间etv的过程,就是按照拓扑排序的顺序依次求。
假如我们现在已经求得v6, v7 顶点的ltv值,现在要求v4 的ltv 值,由邻接表可得到v4 有两条弧<v4, v6>, <v4, v7>,可以得到
ltv[4] = min(ltv[7] - 4, ltv[6] - 9) = 15(再延迟工程v7就要耽搁了),如图7-9-8所示。
可以发现,在计算ltv时,其实是把拓扑序列倒过来进行而已,因此可以得到计算顶点vk最晚发生时间即求ltv[k] 的公式是:
以上部分信息来自大话数据结构
接下来看看怎么实现:
#include <iostream>#include <stdio.h>#include <stack>using namespace std;const int MAXSIZE=100;typedef struct EdgeNode //边表结点{ int adjvex; //存储该顶点的下标 int weight; //对应弧的权值 struct EdgeNode *next;}EdgeNode;typedef struct VertexNode //顶点表结点{ int in; //入度 int data; //顶点值 EdgeNode *firstedge; //边表头指针}VertexNode,AdjList[MAXSIZE];typedef struct{ AdjList adjList;//顶点表结点合集数组 int numVertexes,numEdges; //顶点数和边数}graphAdjList,*GraphAdjList;int *etv,*ltv; //事件最早发生时间和最迟发生时间数组int *stack2; //用于逆拓扑排序的栈int top2; //用于stack2的栈指针bool TopologicalSort(GraphAdjList &GL){ EdgeNode *e; int k,gettop; int top=0; int count=0; int *stack; //拓扑排序栈 stack=(int *)malloc(GL->numVertexes*sizeof(int)); for (int i=0; i<GL->numVertexes; i++) { if(GL->adjList[i].in==0) //入度为0的元素进栈 { stack[++top]=i; } } top2=0; etv=(int*)malloc(sizeof(int)*GL->numVertexes); for (int i=0; i<GL->numVertexes; i++) { etv[i]=0; //初始化 } stack2=(int*)malloc(sizeof(int)*GL->numVertexes); while (top!=0) { gettop=stack[top--]; count++; //元素个数计数,判断是否存在环 stack2[++top2]=gettop; //存入逆拓扑排序栈中 for (e=GL->adjList[gettop].firstedge; e; e=e->next) { k=e->adjvex; if(!(--GL->adjList[k].in)) { stack[++top]=k; } if(etv[gettop]+e->weight>etv[k]) //更新最早发生时间 { etv[k]=etv[gettop]+e->weight; } } } if(count<GL->numVertexes) { return false; } return true;}void CriticalPath(GraphAdjList &GL){ EdgeNode *e; int gettop,k; int ete,lte; TopologicalSort(GL); ltv=(int*)malloc(sizeof(int)*GL->numVertexes); //最迟发生时间数组 for (int i=0; i<GL->numVertexes; i++) { ltv[i]=etv[stack2[GL->numVertexes]]; //初始化终点的最早发生时间,终点的最早发生时间和最迟发生时间是一致的(终点不一定是n-1,而是拓扑排序的最后一个数) } while (top2!=0) { gettop=stack2[top2--]; for (e=GL->adjList[gettop].firstedge; e; e=e->next) { k=e->adjvex; if(ltv[k]-e->weight<ltv[gettop]) //更新最迟发生时间 { ltv[gettop]=ltv[k]-e->weight; //可以达到的最晚时间 } } } for (int j=0; j<GL->numVertexes; j++) { for (e=GL->adjList[j].firstedge; e; e=e->next) { k=e->adjvex; ete=etv[j]; lte=ltv[k]-e->weight; //此条路线的最晚时间 if(ete==lte) //判断是否是关键路径 { printf("%d %d %d\n",GL->adjList[j].data,GL->adjList[k].data,e->weight); } } }}void Init(GraphAdjList &GL){ GL=(GraphAdjList)malloc(sizeof(graphAdjList));}int main(){ GraphAdjList GL; EdgeNode *p; int u,v,w; Init(GL); printf("请输入顶点个数和边的个数:"); scanf("%d%d",&GL->numVertexes,&GL->numEdges); printf("请输入边的顶点,及其权值 u,v,w:\n"); for (int i=0; i<GL->numVertexes; i++) { GL->adjList[i].in=0; GL->adjList[i].data=i; GL->adjList[i].firstedge=NULL; } for (int i=0; i<GL->numEdges; i++) { p=(EdgeNode *)malloc(sizeof(EdgeNode)); scanf("%d%d%d",&u,&v,&w); p->adjvex=v; p->weight=w; p->next=GL->adjList[u].firstedge; GL->adjList[u].firstedge=p; GL->adjList[v].in++; } printf("关键路径及其权值:\n"); printf("-----\n"); CriticalPath(GL); return 0;}
按照上图运行结果:
这就是按照数据结构这本书的要求来安排数据存储形式,感觉好庞大,把邻接表搞的很复杂,其实可以写简略一点的,也来练练手吧。
数组形式的邻接表+先求出拓扑序列
#include <stdio.h>#include <iostream>#include <stack>using namespace std;const int MAXSIZE=100;int head[MAXSIZE],etv[MAXSIZE],ltv[MAXSIZE],cnt,n,m; //head为表头bool vis[MAXSIZE];stack<int>s; //顺拓扑序列stack<int>s2; //逆拓扑序列struct node{ int v,w; int next;}EdgeNode[MAXSIZE];void AddEdge(int u,int v,int w) //建表{ EdgeNode[cnt].v=v; EdgeNode[cnt].w=w; EdgeNode[cnt].next=head[u]; head[u]=cnt++;}void DFS(int u) //求拓扑序列{ vis[u]=true; for (int i=head[u];i!=-1; i=EdgeNode[i].next) { if(!vis[EdgeNode[i].v]) { DFS(EdgeNode[i].v); } } s.push(u);}void Etv() //求最早发生时间{ for (int i=0; i<n; i++) { etv[i]=0; } while (!s.empty()) { int top=s.top(); s2.push(top); s.pop(); for (int i=head[top]; i!=-1; i=EdgeNode[i].next) { int v=EdgeNode[i].v; if(etv[v]<etv[top]+EdgeNode[i].w) { etv[v]=etv[top]+EdgeNode[i].w; } } }}void Ltv()//最迟发生时间{ for (int i=0; i<n; i++) { ltv[i]=etv[s2.top()]; //初始化为终点的最早时间 } while (!s2.empty()) { int top=s2.top(); s2.pop(); for (int i=head[top]; i!=-1; i=EdgeNode[i].next) { int v=EdgeNode[i].v; if(ltv[top]>ltv[v]-EdgeNode[i].w) { ltv[top]=ltv[v]-EdgeNode[i].w; } } }}void CriticalPath(){ Etv(); Ltv(); for (int i=0; i<n; i++) { for (int j=head[i]; j!=-1; j=EdgeNode[j].next) { int ete,lte,v; v=EdgeNode[j].v; ete=etv[i]; lte=ltv[v]-EdgeNode[j].w; if(ete==lte) //判断是否是关键路径 { printf("%d %d %d\n",i,v,EdgeNode[j].w); } } }}int main(){ int degree[MAXSIZE]; printf("请输入顶点个数和边的个数:"); scanf("%d%d",&n,&m); printf("请输入边的顶点,及其权值 u,v,w:\n"); memset(head, -1, sizeof(head)); memset(vis, false, sizeof(vis)); memset(degree, 0, sizeof(degree)); cnt=0; for (int i=0;i<m; i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); AddEdge(u,v,w); degree[v]++; } for (int i=0; i<n; i++) { if(!degree[i]) { DFS(i); //从入度为0的点出发 break; } } printf("-------\n"); CriticalPath(); return 0;}
- 图论_关键路径
- 图论--关键路径
- 图论 关键路径
- 图论-关键路径(sanbin)
- 关键路径
- 关键路径
- 关键路径
- 关键路径
- 关键路径
- 关键路径
- 关键路径
- 关键路径
- 关键路径
- 关键路径
- 关键路径
- 关键路径
- 关键路径
- 关键路径
- 第四章、知识导图 字符串和多维数组
- eclipse可视化开发插件的安装
- [leetcode 128] Longest Consecutive Sequence
- 1月5日投资策略预测:
- frame\bounds\center的区别
- 图论--关键路径
- Web_PHP_PHP XML Expat 解析器浅谈;
- 几个ADB常用命令
- java基础学习第一天、第二天
- Boost练习2——时间和日期2
- Java创建帮助文档Doc步骤
- dfghdfhfgjghjghjkghk
- 黑马程序员————继承,抽象,接口
- fhgdhfgjfgjghjghhj