圖應用之關鍵路徑(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;
}


測試結果:

相關文章
相關標籤/搜索