图应用之关键路径(Critical Path)

以前咱们介绍过,在一个工程中咱们关心两个问题:算法

(1)工程是否顺利进行编程

(2)整个工程最短期。数组

 

以前咱们优先关心的是顶点(AOV),一样咱们也能够优先关心边(同理有AOE)。(Activity On Edge Network)测试

看看百度百科上解释:指针

AOE网:Activity on edge networkhtm

若在带权的有向图中,以顶点表示事件,以有向边表示活动,边上的权值表示活动的开销(如该活动持续的时间),则此带权的有向图称为AOE网。blog

若是用AOE网来表示一项工程,那么,仅仅考虑各个子工程之间的优先关系还不够,更多的是关心整个工程完成的最短期是多少;排序

哪些活动的延期将会影响整个工程的进度,而加速这些活动是否会提升整个工程的效率。队列

所以,一般在AOE网中列出完成预约工程计划所须要进行的活动,每一个活动计划完成的时间,要发生哪些事件以及这些事件与活动之间的关系,事件

从而能够肯定该项工程是否可行,估算工程完成的时间以及肯定哪些活动是影响工程进度的关键。

很显然,顶点表示事件,边表示活动,边的权则表示活动持续时间。

AOE通常用来估算工程的完成时间。

AOE表示工程的流程,把没有入边的称为始点或者源点,没有出边的顶点称为终点或者汇点。通常状况下,工程只有一个开始,一个结束,

因此正常状况下,AOE只有一个源点一个汇点。

 

AOV和AOE的区别:

1.AOV用顶点表示活动的网,描述活动之间的制约关系。

2.AOE用边表示活动的网,边上的权值表示活动持续的时间。

AOE 是创建在子过程之间的制约关系没有矛盾的基础之上,再来分析整个过程须要的时间。

 

AOE研究:

a.完成整个过程至少须要多长时间。

b.哪些活动影响工程的进度?

 

关键路径:从源点到汇点具备最大长度的路径。这个概念要清楚,一个工程不必定有一条关键路径,可能会有多条。

关键活动:关键路径上的活动(边)。

针对上面AOE所关心的问题,要想缩短工程时间,就缩短关键路径上的过程便可。(缩短后可能出现以前的关键路径变成了非关键路径)

 

因为AOE网上全部的活动是能够并行进行。这里举一个例子,组装一个变形金刚,须要头,左膀右臂,身体,左腿右腿。

咱们能够有两种方法:1.先作好头,作左手臂,作右手臂,作身体,作左腿,作右腿,而后再组装。

          2.同时作头、手臂、身体、腿的部分,每有一个头、两个手臂、两个腿和一个身体的时候,就能够组装了。

方法1.如咱们计算机中的串行运行。这样时间开销是累加的。

方法2.如咱们计算机中的并行运行。这样时间开销能够立体应用。在此方法中,同时作各个部位时间不一样,好比作头时间最长,那么整个一个

变形金刚所用的时间决定与作头的时间,若是作手臂的时间是作头时间的一半,那么就是说作手臂的时间点能够在头作了一半的时候。只要不超过这个

时间点,手臂部分是不会影响整个工程的进度的。

 

这里定义四个定义:前两个针对顶点,后两个针对边

事件最先开始时间:顶点Vi最先发生的时间。

事件最晚开始时间:顶点Vi最晚发生的时间,超出则会延误整个工期。

活动的最先开始时间:边Eg最先发生时间。

活动的最晚开始时间:边Eg最晚发生时间。不推迟工期的最晚开工时间。

 

下面这个例子说明一下:

说明:上图中J中为49,是最先开始时间。这里能够看到最先开始时间,就是完要成该顶点,前面的全部到该点的路径都要已经完成。因此取路径最大那一条。

补充说明:

事件最先开始时间:例子图中,F点,ACF(9) 和 ADF(19),到达F点时候,保证AC和AD都完成,这样 F才能开始,因此F点的最先开始时间取最大值,即19.

       能够看出,要求出到某一点的最先开始时间,则须要将汇于该点的全部路径的时间求出来,取最大值。

事件最迟开始时间:这里是反着推,好比H点最迟开始时间,H到J 与 H到I到J两条路径,39 和 44,所谓最迟开始时间,就是超过这个时间就会影响整个工程进度,

         而这个时间是时间点,是从源点工程开始计时的,因此对于H点,39和44是相对于源点,若是取44,则H-J这条路径就会拖延,最迟开始时间选择最小值。

 

关键路径的特色:咱们寻找关键路径——关键路径就是关键活动(顶点与顶点之间的边组成),就是咱们怎么判断该顶点是否为关键活动(边)的顶点,即判断边是否为关键活动。

        前面定义过,关键路径就是图中从源点到汇点最长(权值最大)的路径。

        这条路径就决定了整个工程的工期,这说明一个什么问题?

        关键路径上的顶点与顶点之间的活动的应该最先开始和最迟开始时间是相等的,

        若是不等那么说明活动还有余额时间(在最先开始时间和最迟开始时间之间能够任选一个时间点开始),这说明还有其余活动是决定这个工程时间的,那就不是关键路径了。

 

算法思想:

要准备两个数组,a:最先开始时间数组etv,b:最迟开始时间数组。(针对顶点即事件而言)

1.从源点V0出发,令etv[0](源点)=0,按拓扑有序求其他各顶点的最先发生时间etv[i](1 ≤ i ≤ n-1)。同时按照上一章

拓扑排序的方法检测是否有环存在。

2.从汇点Vn出发,令ltv[n-1] = etv[n-1],按拓扑排序求各个其他各顶点的最迟发生时间ltv[i](n-2 ≥ i ≥ 2);

3.根据各顶点的etv和ltv数组的值,求出弧(活动)的最先开工时间和最迟开工时间,求每条弧的最先开工时间和最迟开工时间是否相等,若相等,则是关键活动。

注意:1,2 完成点(事件)的最先和最迟。3根据事件来计算活动最先和最迟,从而求的该弧(活动)是否为关键活动。

 

 

 

关键代码:

1.对图进行拓扑排序,存储了拓扑排序的顺序,做为关键路径的计算最迟开始时间的依据。

int TopplogicalSort(GraphAdjList *g)
{
    int count=0;
    eNode *e=NULL;

    StackType *stack=NULL;
    StackType top=0;
    stack = (StackType *)malloc((*g).numVextexs*sizeof(StackType));
    
    int i;
    
    //初始化拓扑序列栈
    g_topOfStk = 0;
    //开辟拓扑序列栈对应的最先开始时间数组
    g_etv = (int *)malloc((*g).numVextexs*sizeof(int));
    //初始化数组
    for (i=0;i<(*g).numVextexs;i++)
    {
        g_etv[i]=0;
    }
    //开辟拓扑序列的顶点数组栈
    g_StkAfterTop = (int *)malloc(sizeof(int)*(*g).numVextexs);
    
    
    

    for (i=0;i<(*g).numVextexs;i++)
    {
        if (!(*g).adjList[i].numIn)
        {
            stack[++top] = i;
    //        printf("init no In is %c\n",g_init_vexs[i]);
        }
    }
    

    while(top)
    {
        int geter = stack[top];
        top--;

        //把拓扑序列保存到拓扑序列栈,为后面作准备
        g_StkAfterTop[g_topOfStk++] = geter;
        
        printf("%c -> ",g_init_vexs[(*g).adjList[geter].idx]);
        count++;

        //获取当前点出度的点,对出度的点的入度减一(当前点要出图)。
        //获取当前顶点的出度点表
        e = (*g).adjList[geter].fitstedge;
        while(e)
        {
            int eIdx = e->idx;
            //选取的出度点的入度减一
            int crntIN = --(*g).adjList[eIdx].numIn;
            if (crntIN == 0)
            {
                //若是为0,则说明该顶点没有入度了,是下一轮的输出点。
                stack[++top] = eIdx;
        //        printf("running the vex is %c\n",g_init_vexs[e->idx]);
            }

            //求出关键路径
            if ((g_etv[geter] + e->weigh) > g_etv[eIdx])
            {
                g_etv[eIdx] = g_etv[geter] + e->weigh;
            }

            e = e->next;
        }
    }
    if (count < (*g).numVextexs)//若是图自己就是一个大环,或者图中含有环,这样有环的顶点不会进栈而被打印出来。
    {
        return false;
    }
    else
    {
        printf("finish\n");
        return true;
    }
    
}

 

 2.关键路径代码:

void CriticalPath(GraphAdjList g)
{
    int i;
    int geter;
    eNode *e = NULL;
    g_topOfStk--;
    //1.初始化最迟开始时间数组(汇点的最先开始时间(初值))
    g_ltv = (int *)malloc(sizeof(int)*g.numVextexs);
    for (i=0;i<g.numVextexs;i++)
    {
        g_ltv[i] = g_etv[g.numVextexs-1];
    }

    //2.求每一个点的最迟开始时间,从汇点到源点推。
    while (g_topOfStk)
    {
        //获取当前出栈(反序)的序号
        geter = g_StkAfterTop[g_topOfStk--];
        //对每一个出度点
        if (g.adjList[geter].fitstedge != NULL)
        {
            e = g.adjList[geter].fitstedge;
            while(e != NULL)
            {
                int eIdx = e->idx;
                if (g_ltv[eIdx] - e->weigh < g_ltv[geter])
                {
                    g_ltv[geter] = g_ltv[eIdx] - e->weigh;
                }
                e = e->next;
            }
        }
    }

    int ete,lte;//活动最先开始和最迟开始时间

    

    printf("start:->");
    //3.求关键活动,即ltv和etv相等的
    for (i=0;i<g.numVextexs;i++)
    {
        if (g.adjList[i].fitstedge)
        {
            e = g.adjList[i].fitstedge;
            while(e)
            {
                int eIdx = e->idx;
                //活动(i->eIdx)最先开始时间:事件(顶点) i最先开始时间
                ete = g_etv[i];
                //活动(i->eIdx)最迟开始时间:事件(顶点) eIdx 最迟开始时间 减去 活动持续时间
                lte = g_ltv[eIdx] - e->weigh; 
                if (ete == lte)
                {
                    printf("(%c - %c)->",g_init_vexs[i],g_init_vexs[eIdx]);
                }
                e= e->next;
            }
        }
    }
    printf(" end\n");
}

 

编程所用的图:

拓扑排序结果:

 

过程:

1.从J开始,无后继,不作任何事情;

2.G,G的ltv为27,ltv-weight = 27-2 < 27,因此G的ltv为25;

3.I,I的ltv为27,ltv-weight = 27 -3 < 27,因此I的ltv为24;

4.H,H的ltv为24(I的ltv),24-5 < 24,因此H 的ltv为19;

依次类推。。。

 

完成top排序和关键路径后:

全局存放各个顶点的最先开始和最迟开始时间:

完整代码:

// grp-top.cpp : 定义控制台应用程序的入口点。
//
// grp-top.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdlib.h>


#define MAXVEX 100
#define IFY 65535


typedef char VertexType;
typedef int  EdgeType;
typedef int  IdxType;
typedef int QueueType;
typedef int StackType;


//-------
int *g_etv = NULL;
int *g_ltv = NULL;
int *g_StkAfterTop;
int g_topOfStk;


///---------------------------------------
//边节点
typedef struct EdgeNode{
    IdxType idx;
    int weigh;
    struct EdgeNode* next;
}eNode;

//顶点节点
typedef struct VexNode{
    int numIn;        //入度数量
    IdxType idx;
    eNode *fitstedge;
}vNode;

//图的集合:包含了一个顶点数组
typedef struct {
    vNode adjList[MAXVEX];
    int numVextexs,numEdges;
}GraphAdjList;

///-----------------------------------
/*VertexType g_init_vexs[MAXVEX] = {'A','B','C','D','E','F','G','H','I','J','K','L'};

char *g_input[] = {
    "A->B->C->D",
    "B->E",
    "C->F->I->J",
    "D->E->I->J",
    "E",
    "F->K",
    "G->F->H->K",
    "H->I",
    "I->J->L",
    "J->E->K",
    "K->L",
    "L"
};*/

///-----------------------------------
VertexType g_init_vexs[MAXVEX] = {'A','B','C','D','E','F','G','H','I','J'};

char *g_input[] = {
    "A->B->C",
    "B->D->E",
    "C->D->F",
    "D->E",
    "E->G->H",
    "F->H",
    "G->J",
    "H->I",
    "I->J",
    "J",
    NULL
};

char *g_input_weigh[] = {
    "3,4",//A
    "5,6",//B
    "8,7",//C
    "3",//D
    "9,4",//E
    "6",//F
    "2",//G
    "5",//H
    "3",//I
    " ",//J
    NULL
};
//===============================================================
//队列

//队列节点
typedef struct Node {
    QueueType data;
    struct Node *next;
}QNode,*qQNode;

//队列指示
typedef struct {
    int length;
    qQNode frnt,rear;    
}spQueue;

void init_Queue(spQueue *Q)
{
    (*Q).frnt = NULL;
    (*Q).rear = NULL;
    (*Q).length = 0;
}
bool isEmptyQueue(spQueue Q)
{
    if (Q.length == 0)
    {
        return true;
    }
    return false;
}
//进队
void unshiftQueue(spQueue *Q,QueueType elem)
{
    //队列空
    if (isEmptyQueue(*Q))
    {
        qQNode n = (qQNode)malloc(sizeof(QNode));
        n->data = elem;
        n->next = NULL;

        (*Q).frnt = n;
        (*Q).rear = n;
        (*Q).length = 1;
    }
    else
    {
        qQNode n = (qQNode)malloc(sizeof(QNode));
        n->data = elem;
        n->next = NULL;

        (*Q).rear->next = n;

        (*Q).rear = n;
        (*Q).length++;
    }
}

//出队
QueueType shiftQueue(spQueue *Q)
{
    if (isEmptyQueue(*Q))
    {
        printf("Warning:Queue is empty!!!\n");
        return NULL;
    }
    if ((*Q).length == 1)
    {
        QueueType sh = (*Q).frnt->data;
        (*Q).frnt = NULL;
        (*Q).rear = NULL;
        (*Q).length = 0;
        return sh;
    }
    QueueType sh = (*Q).frnt->data;
    (*Q).frnt = (*Q).frnt->next;
    (*Q).length--;

    return sh;
}

//打印队列
void prt_que(spQueue que)
{
    if (isEmptyQueue(que))
    {
        return ;
    }
    qQNode pos = que.frnt;
    while(que.rear->next != pos && pos != NULL)
    {
        printf(" %d ",pos->data);
        pos = pos->next;
    }
    printf("\n");
}
//===============================================================

///-------
//由节点找节点的序号
IdxType strFindIdx(char ch)
{
    int i=0;
    VertexType *p = g_init_vexs;
    while(p != NULL)
    {
        if(*p == ch)
        {
            return i;
        }
        p++;
        i++;
    }
    return i;
}

//由序号找节点
VertexType idxFindStr(IdxType i)
{
    return g_init_vexs[i];
}

void prt_strings(char *p)
{
    char *pos = p;
    while (NULL != *pos)
    {
        printf("%c",*pos);
        pos++;
    }
    printf("\n");
}

void prt_strArrays(char *p[],int num)
{
    char **pos = p; 
    int i=0;
    while( *pos != NULL && i < num)
    {
        prt_strings(*pos);
        pos++;
        i++;
    }
}

//本身规定:顶点只能是大写。
bool isVexter(char p)
{
    if (p>='A' && p<='Z')
    {
        return true;
    }
    return false;
}

bool isNumeric(char p)
{
    if (p >= '0' && p <= '9')
    {
        return true;
    }
    return false;
}

void init_GrapAdjList(GraphAdjList *g,char **str,char **wstr)
{
    char **pos = str;
    
    int cnt=0;
    int vcnt = 0;
    char **wpos = wstr;//weight value

    //入度清零
    int i;
    for (i=0;i<MAXVEX;i++)
    {
        (*g).adjList[i].numIn = 0;
    }

    while (*pos != NULL) //g_input的每行的首指针
    {
        int i=0;
        while(**pos != NULL) //g_input的每行字母
        {
            if(isVexter(**pos)) //判断是否为顶点(我规定‘A’-‘Z’之间为顶点标志)
            {
                if (i == 0) //创建顶点的节点
                {
                    (*g).adjList[cnt].idx = strFindIdx(**pos);
                    (*g).adjList[cnt].fitstedge = NULL;
                    
                    i=1;
                }
                else if(i == 1) //创建第一个边的节点
                {
                    eNode* n = (eNode*)malloc(sizeof(eNode));
                    n->idx = strFindIdx(**pos);
                    n->next = NULL;

                    //weight
                    while (!isNumeric(**wpos))
                    {
                        (*wpos)++;
                    }
                    n->weigh = **wpos-'0';
                    (*wpos)++;

                    (*g).adjList[cnt].fitstedge = n;
                    i=2;

                    //添加入度
                    int iidx = strFindIdx(**pos);
                    (*g).adjList[iidx].numIn++;
                }
                else //边节点链接到前一个边节点上
                {    
                    eNode* n = (eNode*)malloc(sizeof(eNode));
                    n->idx = strFindIdx(**pos);
                    n->next = NULL;

                    //weight
                    while (!isNumeric(**wpos))
                    {
                        (*wpos)++;
                    }
                    n->weigh = **wpos-'0';
                    (*wpos)++;

                    //first splist
                    eNode *r = (*g).adjList[cnt].fitstedge;
                    while (r->next != NULL)
                    {
                        r = r->next;
                    }
                    r->next = n;

                    //添加入度
                    int iidx = strFindIdx(**pos);
                    (*g).adjList[iidx].numIn++;
                }
            }
            (*pos)++; 
        }

        wpos++;
        cnt++;
        pos++;
        
    }
    (*g).numVextexs = cnt;
}

int TopplogicalSort(GraphAdjList *g)
{
    int count=0;
    eNode *e=NULL;

    StackType *stack=NULL;
    StackType top=0;
    stack = (StackType *)malloc((*g).numVextexs*sizeof(StackType));
    
    int i;
    
    //初始化拓扑序列栈
    g_topOfStk = 0;
    //开辟拓扑序列栈对应的最先开始时间数组
    g_etv = (int *)malloc((*g).numVextexs*sizeof(int));
    //初始化数组
    for (i=0;i<(*g).numVextexs;i++)
    {
        g_etv[i]=0;
    }
    //开辟拓扑序列的顶点数组栈
    g_StkAfterTop = (int *)malloc(sizeof(int)*(*g).numVextexs);
    
    
    

    for (i=0;i<(*g).numVextexs;i++)
    {
        if (!(*g).adjList[i].numIn)
        {
            stack[++top] = i;
    //        printf("init no In is %c\n",g_init_vexs[i]);
        }
    }
    

    while(top)
    {
        int geter = stack[top];
        top--;

        //把拓扑序列保存到拓扑序列栈,为后面作准备
        g_StkAfterTop[g_topOfStk++] = geter;
        
        printf("%c -> ",g_init_vexs[(*g).adjList[geter].idx]);
        count++;

        //获取当前点出度的点,对出度的点的入度减一(当前点要出图)。
        //获取当前顶点的出度点表
        e = (*g).adjList[geter].fitstedge;
        while(e)
        {
            int eIdx = e->idx;
            //选取的出度点的入度减一
            int crntIN = --(*g).adjList[eIdx].numIn;
            if (crntIN == 0)
            {
                //若是为0,则说明该顶点没有入度了,是下一轮的输出点。
                stack[++top] = eIdx;
        //        printf("running the vex is %c\n",g_init_vexs[e->idx]);
            }

            //求出关键路径
            if ((g_etv[geter] + e->weigh) > g_etv[eIdx])
            {
                g_etv[eIdx] = g_etv[geter] + e->weigh;
            }

            e = e->next;
        }
    }
    if (count < (*g).numVextexs)//若是图自己就是一个大环,或者图中含有环,这样有环的顶点不会进栈而被打印出来。
    {
        return false;
    }
    else
    {
        printf("finish\n");
        return true;
    }
    
}
void CriticalPath(GraphAdjList g)
{
    int i;
    int geter;
    eNode *e = NULL;
    g_topOfStk--;
    //1.初始化最迟开始时间数组(汇点的最先开始时间(初值))
    g_ltv = (int *)malloc(sizeof(int)*g.numVextexs);
    for (i=0;i<g.numVextexs;i++)
    {
        g_ltv[i] = g_etv[g.numVextexs-1];
    }

    //2.求每一个点的最迟开始时间,从汇点到源点推。
    while (g_topOfStk)
    {
        //获取当前出栈(反序)的序号
        geter = g_StkAfterTop[g_topOfStk--];
        //对每一个出度点
        if (g.adjList[geter].fitstedge != NULL)
        {
            e = g.adjList[geter].fitstedge;
            while(e != NULL)
            {
                int eIdx = e->idx;
                if (g_ltv[eIdx] - e->weigh < g_ltv[geter])
                {
                    g_ltv[geter] = g_ltv[eIdx] - e->weigh;
                }
                e = e->next;
            }
        }
    }

    int ete,lte;//活动最先开始和最迟开始时间

    

    printf("start:->");
    //3.求关键活动,即ltv和etv相等的
    for (i=0;i<g.numVextexs;i++)
    {
        if (g.adjList[i].fitstedge)
        {
            e = g.adjList[i].fitstedge;
            while(e)
            {
                int eIdx = e->idx;
                //活动(i->eIdx)最先开始时间:事件(顶点) i最先开始时间
                ete = g_etv[i];
                //活动(i->eIdx)最迟开始时间:事件(顶点) eIdx 最迟开始时间 减去 活动持续时间
                lte = g_ltv[eIdx] - e->weigh; 
                if (ete == lte)
                {
                    printf("(%c - %c)->",g_init_vexs[i],g_init_vexs[eIdx]);
                }
                e= e->next;
            }
        }
    }
    printf(" end\n");
}


int _tmain(int argc, _TCHAR* argv[])
{
    GraphAdjList grp;
    printf("print Matix: of Vextexs:\n");
    prt_strArrays(g_input,10);
    printf("print Matix: of Weigh:\n");
    prt_strArrays(g_input_weigh,10);

    init_GrapAdjList(&grp,g_input,g_input_weigh);
    printf("Top sort:\n");
    if (!TopplogicalSort(&grp))
    {
        printf("grp wrong!\n");
    }
    
    CriticalPath(grp);

    getchar();
    return 0;
}


测试结果:

相关文章
相关标签/搜索