1. 程式人生 > >圖——基本的圖演算法(四)關鍵路徑

圖——基本的圖演算法(四)關鍵路徑

圖——基本的圖演算法(四)關鍵路徑

1. 基本概念

(1)AOV網(Activity On Vertex Network)
AOV網是一個表示工程的有向圖中,其中的頂點用來表示活動,弧則用來表示活動之間的優先關係。舉個簡單的例子,假定起床後可以邊煮水,邊刷牙洗臉,但洗臉要在刷牙後,煮好水,刷好牙洗好臉以後,就可以喝水了,這個活動的AOV圖如下(其中的每個頂點都表示一個活動,而頂點之間的弧表示了活動之間的執行先後順序):
在這裡插入圖片描述

(2)AOE網( Activity On Edge Network)
AOE網是一個表示工程的帶權有向圖,其中的頂點用來表示某個事件,弧用來表示活動,弧上的權值用來表示活動持續的時間。例如上述例子的AOE網如下:
在這裡插入圖片描述

(3)AOE網和AOV網的區別
AOV網:其頂點用來表示活動。AOE網是用來表示活動之間的制約關係。
AOE網:頂點表示事件,邊表示活動,邊上的權值用來表示活動持續的時間。AOV網是用來分析工程至少需要花多少時間完成,或是為了縮短時間需要加快哪些活動等問題。

(4)源點、匯點、路徑長度
在AOE網中,
始點源點:入度為0的頂點,它表示一個工程的開始;
終點匯點:出度為0的頂點,它表示一個工程的結束;
路徑長度:路徑上各個活動所持續的時間之和。

(5)關鍵路徑、關鍵活動
在AOE網中,從源點到匯點具有最大長度的路徑稱為關鍵路徑,在關鍵路徑上的活動稱為關鍵活動

2. 關鍵路徑演算法

2.1 基本思想

(1)要找出一個AOE網中的關鍵路徑,就要先找出網裡的關鍵活動,這些關鍵活動間的路徑就是關鍵路徑。

(2)判斷一個活動是不是關鍵活動,方法是要先找到所有活動的最早開始時間和最晚開始時間,並且對它們進行比較,如果二者相等(意味著這個活動在該工程中沒有時間鬆動),說明這個活動是關鍵活動。

(3)對於活動<Vi, Vj>,它最早的開始時間等於事件Vi最早的發生時間earlist[Vi](事件v的最早發生時間用earlist[v])。假設E[Vi]表示所有到達頂點Vi的弧的集合,len<Vi, Vj>表示活動<Vi, Vj>的持續時間,那麼:
在這裡插入圖片描述


注意,這裡假設頂點下標從0開始,所以Vi==0,則表示它是源點,因此最早的開始時間為0;當某個頂點不是源點,那麼只有在它前面的事件都發生完後,才能輪到這個事件,所以用了max。

(4)對於活動<Vi, Vj>,它最晚的開始時間等於事件Vj最晚的發生時間減去這個活動的持續事件,即latest[Vj]-len<Vi, Vj>(事件v的最晚的發生時間用latest[v])。假設S[Vj]表示所有從頂點Vj出發的弧的集合,len<Vj, Vk>表示活動<Vj, Vk>的持續時間,那麼:
在這裡插入圖片描述

2.2 演算法實現

(1)資料結構

typedef struct EdgeListNode{ //邊表結點
    int adjId;
    int weight;
    EdgeListNode* next;
};

typedef struct VertexListNode{ //頂點表結點
    int in; //入度
    int data;
    EdgeListNode* firstadj; //指向其邊表
};

typedef struct GraphAdjList{ //圖結構
    int vertexnumber; //頂點個數
    int edgenumber;
    VertexListNode vertextlist[Maximum];
};

(2)具體實現
1)由基本思路可以知道,要求一個AOE網的關鍵路徑,步驟如下:
A. 首先初始化每個頂點的最早開始為0,然後對AOE網進行拓撲排序,在排序的過程中獲取每個頂點的最早開始時間;
B. 獲取拓撲排序後,初始化每個頂點的最晚開始時間為匯點的最早開始時間,並從AOE網的匯點開始,從後往前,對每個頂點找到求其最晚開始時間;
C. 遍歷圖中的每條邊(方法是遍歷圖中每個頂點的邊表),求其最早開始時間和最晚開始時間,如果二者相等,則這是個關鍵活動,將其加入關鍵路徑中。

2)程式碼

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<map>
#include<vector>
#include<sstream>
#include<list>
#include<stdlib.h>
#include<queue>
using namespace std;

#define Maximum 1000

typedef struct EdgeListNode{
    int adjId;
    int weight;
    EdgeListNode* next;
};

typedef struct VertexListNode{
    int in;
    int data;
    EdgeListNode* firstadj;
};

typedef struct GraphAdjList{
    int vertexnumber;
    int edgenumber;
    VertexListNode vertextlist[Maximum];
};

//拓撲排序,返回拓撲排序結果,earlist儲存每個頂點的最早開始時間
vector<int> ToplogicalSort(GraphAdjList g, int *earlist) {
    vector<int>v; //儲存拓撲排序結果
    queue<int>q; //儲存入度為0的頂點
    EdgeListNode *temp;
    int i, j, k, ans;

    ans = 0;
    v.clear();
    while(!q.empty()) {
        q.pop();
    }

	//找出所有入度為0的頂點
    for(i=1; i<=g.vertexnumber; i++) {
        if(g.vertextlist[i].in == 0) {
            q.push(i);
        }
    }

    while(!q.empty()) {
        i = q.front();
        v.push_back(i);
        q.pop();
        ans++;
        temp = g.vertextlist[i].firstadj;
        while(temp != NULL) {
            k = earlist[i] + temp->weight;
            if(k > earlist[temp->adjId]) {
                earlist[temp->adjId] = k;
            }
            j = --g.vertextlist[temp->adjId].in;
            if(j == 0) {
                q.push(temp->adjId);
            }
            temp = temp->next;
        }
    }

    if(ans < g.vertexnumber) {
        v.clear();
    }
    return v;
}

//求關鍵路徑,返回儲存關鍵路徑頂點的vector
vector<int> CriticalPath(GraphAdjList g) {
    vector<int>ans;
    vector<int>path;
    int i, j, k, *earlist, *latest;
    EdgeListNode *temp;

	//earlist儲存每個頂點的最早開始時間
	//latest儲存每個頂點的最晚開始時間
    earlist = (int*)malloc(sizeof(int) * (g.vertexnumber+1));
    latest = (int*)malloc(sizeof(int) * (g.vertexnumber+1));
    path.clear();
    for(i=1; i<=g.vertexnumber; i++) {
        earlist[i] = 0;
    }

    ans = ToplogicalSort(g, earlist);
    for(i=1; i<=g.vertexnumber; i++) {
        latest[i] = earlist[ans[g.vertexnumber-1]];
    }
    for(i=g.vertexnumber; i>0; i--) {
        temp = g.vertextlist[i].firstadj;
        while(temp!=NULL) {
            if(latest[temp->adjId] - temp->weight < latest[i]) {
                latest[i] = latest[temp->adjId] - temp->weight;
            }
            temp = temp->next;
        }
    }

	//遍歷每條邊
    for(i=1; i<=g.vertexnumber; i++) {
        temp = g.vertextlist[i].firstadj;
        while(temp != NULL) {
            j = earlist[i];
            k = latest[temp->adjId] - temp->weight;
            if(j == k) { //是關鍵活動
       			//把該活動的兩個頂點加入path
                path.push_back(i);
                path.push_back(temp->adjId);
            }
            temp = temp->next;
        }
    }
    return path;
}

(3)測試
在這裡插入圖片描述

int main() {
    GraphAdjList g;
    EdgeListNode *e;
    int i, j, k;

    g.vertexnumber = 5;
    g.edgenumber = 7;


    for(i=1; i<=g.vertexnumber; i++) {
        g.vertextlist[i].data = i;
        g.vertextlist[i].firstadj = NULL;
    }

    g.vertextlist[1].in = 0;
    g.vertextlist[2].in = 1;
    g.vertextlist[3].in = 2;
    g.vertextlist[4].in = 2;
    g.vertextlist[5].in = 1;

    e = (EdgeListNode*)malloc(sizeof(EdgeListNode));
    e->adjId = 2; e->weight = 2; e->next = g.vertextlist[1].firstadj; g.vertextlist[1].firstadj = e;

    e = (EdgeListNode*)malloc(sizeof(EdgeListNode));
    e->adjId = 4; e->weight = 1; e->next = g.vertextlist[1].firstadj; g.vertextlist[1].firstadj = e;

    e = (EdgeListNode*)malloc(sizeof(EdgeListNode));
    e->adjId = 5; e->weight = 1; e->next = g.vertextlist[1].firstadj; g.vertextlist[1].firstadj = e;

    e = (EdgeListNode*)malloc(sizeof(EdgeListNode));
    e->adjId = 3; e->weight = 3; e->next = g.vertextlist[2].firstadj; g.vertextlist[2].firstadj = e;

    e = (EdgeListNode*)malloc(sizeof(EdgeListNode));
    e->adjId = 4; e->weight = 5; e->next = g.vertextlist[2].firstadj; g.vertextlist[2].firstadj = e;

    e = (EdgeListNode*)malloc(sizeof(EdgeListNode));
    e->adjId = 3; e->weight = 8; e->next = g.vertextlist[4].firstadj; g.vertextlist[4].firstadj = e;

    e = (EdgeListNode*)malloc(sizeof(EdgeListNode));
    e->adjId = 3; e->weight = 1; e->next = g.vertextlist[5].firstadj; g.vertextlist[5].firstadj = e;

    vector<int>ans;
    ans = CriticalPath(g);

    for(i=0; i<ans.size(); i+=2) {
        cout<<ans[i]<<"->"<<ans[i+1]<<endl;
    }
    cout<<endl;

    return 0;
}