图论--关键路径

来源:互联网 发布:怎么用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;}

0 0
原创粉丝点击