1. 程式人生 > >coding A&D:關鍵路徑(AOE網)

coding A&D:關鍵路徑(AOE網)

1、AOE-網介紹

我們在學習拓撲排序(如果沒學,可以看看這篇部落格:拓撲排序詳解)的時候,已經接觸了什麼是AOV-網,AOV-網是優先考慮頂點的思路,而我們也同樣可以優先考慮邊,這個就是AOE-網的思路。

若在帶權的有向無環圖中,以頂點表示事件,以有向邊表示活動,邊上的權值表示活動的開銷(如該活動持續的時間),則此帶權的有向無環圖稱為AOE網。記住AOE-網只是比AOV-網多了一個邊的權重,而且AOV-網一般是設計一個龐大的工程各個子工程實施的先後順序,而我們的AOE-網就是不僅僅關係整個工程中各個子工程的實施的先後順序,同時也關係整個工程完成最短時間。

因此,通常在AOE網中列出完成預定工程計劃所需要進行的活動,每個活動計劃完成的時間,要發生哪些事件以及這些事件與活動之間的關係,從而可以確定該項工程是否可行,估算工程完成的時間以及確定哪些活動是影響工程進度的關鍵。

AOE-網還有一個特點就是:只有一個起點(入度為0的頂點)和一個終點(出度為0的頂點),並且AOE-網有兩個待研究的問題:

  1. 完成整個工程需要的時間
  2. 哪些活動是影響工程進度的關鍵

2、名詞解釋

這裡寫圖片描述

  • 關鍵路徑:AOE-網中,從起點到終點最長的路徑的長度(長度指的是路徑上邊的權重和)
  • 關鍵活動:關鍵路徑上的邊

假設起點是vo,則我們稱從v0到vi的最長路徑的長度為vi的最早發生時間,同時,vi的最早發生時間也是所有以vi為尾的弧所表示的活動的最早開始時間,使用e(i)表示活動ai最早發生時間,除此之外,我們還定義了一個活動最遲發生時間,使用l(i)表示,不推遲工期的最晚開工時間。我們把e(i)=l(i)的活動ai稱為關鍵活動

,因此,這個條件就是我們求一個AOE-網的關鍵路徑的關鍵所在了。

所以,我們現在要求的就是每弧所對應的e(i)和l(i),求這兩個變數的公式是:

e(i)=ve(j)
l(i)=vl(k)-dut(<j,k>)

變數的介紹:

首先我們假設活動a(i)是弧<j,k>上的活動,j為弧尾頂點,k為弧頭(有箭頭的一邊),
ve(j)代表的是弧尾j的最早發生時間,
vl(k)代表的是弧頭k的最遲發生時間
dut(<j,k>)代表該活動要持續的時間,既是弧的權值

好了,先在我們知道了求e(i)和l(i)就必須先知道各個頂點的ve和vl了,所以下面我們就來求每個頂點ve和vl。其中,我們要知道ve和vl是要分開來求的。

先求ve,從ve(0)=0開始往前推(其實就是從起點開始往後,求各個頂點最早發生時間),公式如下:

ve(j)=Max{ve{i}+dut(<i,j>)};
<i,j>屬於T,j=1,2.....n-1,
其中T是所有以第j個頂點為頭的弧的集合。n為頂點的個數

下面我們繼續求:各個頂點的vl,vl是從vl(n-1)=ve(n-1)往後推進(其實就是從終點開始往前求各個頂點的最遲發生時間,其中終點的ve和vl是相等的)

vl(i)=Min{vl(j)-dut(<i,j>)}
<i,j>屬於S,i=n-2,n-3.....0
其中,S是所有以第i個頂點為尾的弧的集合

3、求關鍵路徑的步驟

  1. 輸入頂點數和邊數,已經各個弧的資訊建立圖
  2. 從源點v1出發,令ve[0]=0;按照拓撲序列往前求各個頂點的ve。如果得到的拓撲序列個數小於網的頂點數n,說明我們建立的圖有環,無關鍵路徑,直接結束程式
  3. 從終點vn出發,令vl[n-1]=ve[n-1],按逆拓撲序列,往後求其他頂點vl值
  4. 根據各個頂點的ve和vl求每個弧的e(i)和l(i),如果滿足e(i)=l(i),說明是關鍵活動。

4、求關鍵路徑的程式碼實現

  • CriticalPath.h檔案的程式碼:
/************************************************************/
/*                程式作者:Willam                          */
/*                程式完成時間:2017/3/6                    */
/*                有任何問題請聯絡:[email protected]       */
/************************************************************/
//@儘量寫出完美的程式

#pragma once
//#pragma once是一個比較常用的C/C++雜注,
//只要在標頭檔案的最開始加入這條雜注,
//就能夠保證標頭檔案只被編譯一次。


/*
求解關鍵路徑問題,
必須是有向無環圖才有關鍵路徑

*/
#include<iostream>
#include<stack>
#include<string>
using namespace std;
//表結點
struct ArcNode {
    int start;    //弧尾的頂點的下標
    int end;    //弧頭的頂點的下標 ,有箭頭的一方
    int weight; //弧的權重
    ArcNode * next; //下一條弧
};
//頭結點
struct Vnode {
    ArcNode * firstarc;  //第一條依附在該該頂點的弧
    string data;
};

class Graph_DG {
private:
    int vexnum; //頂點個數
    int edge;   //邊的條數
    Vnode * arc; //鄰接表
    int *indegree; //各個頂點的入度情況
    stack<int> List; //拓撲序列中各個邊的情況
    int * ve;  //記錄每個頂點的最早發生時間
    int * vl;  //記錄每個頂點最遲發生時間

public:
    Graph_DG(int vexnum, int edge); 
    ~Graph_DG();//解構函式
    bool check_edge_value(int, int, int); //檢查邊的資訊是否合法
    void createGraph();//建立一個新的圖
    void print();//列印圖的鄰接表
    bool topological_sort();
    bool CriticalPath();
};
  • CriticalPath.cpp檔案的程式碼
#include"CriticalPath.h"

Graph_DG::Graph_DG(int vexnum, int edge) {  
        /*
        初始化一些基本的資訊,
        包括邊和頂點個數,各個頂點入度陣列,鄰接表的等
        */
        this->vexnum = vexnum;
        this->edge = edge;
        this->arc = new Vnode[this->vexnum];
        this->indegree = new int[this->vexnum];
        this->ve = new int[this->vexnum];
        this->vl = new int[this->vexnum];
        for (int i = 0; i < this->vexnum; i++) {
            this->indegree[i] = 0;
            this->ve[i] = 0;
            this->arc[i].firstarc = NULL;
            this->arc[i].data = "v" + to_string(i + 1);
        }
}

//釋放記憶體空間
Graph_DG::~Graph_DG() {
    ArcNode * p, *q;
    for (int i = 0; i < this->vexnum; i++) {
        if (this->arc[i].firstarc) {
            p = this->arc[i].firstarc;
            while (p) {
                q = p->next;
                delete p;
                p = q;
            }
        }
    }
    delete[] this->arc;
    delete[] this->indegree;
}
//判斷我們每次輸入的的邊的資訊是否合法
//頂點從1開始編號
bool Graph_DG::check_edge_value(int start, int end,int weight) {
    if (start<1 || end<1 || start>vexnum || end>vexnum || weight < 0) {
        return false;
    }
    return true;
}

void Graph_DG::createGraph() {
    cout << "請輸入每條邊的起點和終點的編號(頂點從1開始編號)以及每條邊的權重" << endl;
    int count = 0; //記錄初始化邊的條數
    int start, end, weight;
    while (count != this->edge) {
        cin >> start >> end >> weight;
        while (!check_edge_value(start, end, weight)) {
            cout << "輸入的資訊不合法,請重新輸入:" << endl;
            cin >> start >> end >> weight;
        }
        ArcNode * temp = new ArcNode;
        temp->start = start-1;
        temp->end = end-1;
        temp->weight = weight;
        temp->next = NULL;
        //如果當前頂點的還沒有邊依附時,
        ++indegree[temp->end];   //對應的弧頭的頂點的入度加1
        if (this->arc[start - 1].firstarc == NULL) {
            this->arc[start - 1].firstarc = temp;
        }
        else {
            ArcNode * now = this->arc[start - 1].firstarc;
            while (now->next) {
                now = now->next;
            }//找到該連結串列的最後一個結點
            now->next = temp;
        }
        ++count;
    }
}
void Graph_DG::print() {
    cout << "圖的鄰接表為:" << endl;
    int  count = 0;
    while (count != this->vexnum) {
        cout << this->arc[count].data << " ";
        ArcNode * temp = this->arc[count].firstarc;
        while (temp) {
            cout << "<"
                << this->arc[temp->start].data
                << ","
                << this->arc[temp->end].data
                << ">="
                << temp->weight
                << " ";
            temp = temp->next;
        }
        cout << "^" << endl;
        ++count;
    }
}

bool Graph_DG::topological_sort() {
    cout << "圖的拓撲序列為:" << endl;
    stack<int> s; //儲存入度為0的頂點下標
    ArcNode * temp;
    int i;
    for (i = 0; i < this->vexnum; i++) {
        if (!indegree[i]) s.push(i); //入度為0 ,則入棧
    }
    //count用於計算輸出的頂點個數
    int count = 0;
    while (!s.empty()) {//如果棧為空,退出迴圈
        i = s.top(); //i等於棧頂的元素
        s.pop();
        cout << this->arc[i].data << " ";//輸出拓撲序列
        temp = this->arc[i].firstarc;
        this->List.push(i);
        while (temp) {
            if (!(--indegree[temp->end])) {//如果入度減少到為0,則入棧
                s.push(temp->end);
            }
            //同時更新ve的值
            if ((ve[i] + temp->weight) > ve[temp->end]) {
                ve[temp->end] = ve[i] + temp->weight;
            }
            temp = temp->next;
        }
        ++count;
    }
    if (count == this->vexnum) {
        cout << endl;
        return true;
    }
    cout << "此圖有環,無拓撲序列" << endl;
    return false;//說明這個圖有環
}

bool Graph_DG::CriticalPath() {

    if (!this->topological_sort()) {
        cout << "此圖有環,無關鍵路徑" << endl;
        return false;
    }
    cout << "圖的關鍵路徑為:" << endl;
    //初始化vl的值都為ve[this->vexnum-1]
    int k;

    for (k = 0; k < this->vexnum; k++) {
        vl[k] = ve[this->vexnum - 1];

    }

    ArcNode * temp;
    while (!this->List.empty()) {
        k = List.top();//從逆拓撲排序開始,求vl
        List.pop();
        temp = this->arc[k].firstarc;
        while (temp) {
            //dut<k,temp->end>,從以第k個頂點為弧尾集合中選擇一個最小的值
            //來作為第i個頂點的最遲發生時間
            if (vl[k] > (vl[temp->end] - temp->weight)) {
                vl[k] = vl[temp->end] - temp->weight;
            }
            temp = temp->next;
        }
    }
    int ee;
    int el;
    for (k = 0; k < this->vexnum; k++) {
        temp= temp = this->arc[k].firstarc;
        while (temp) {
            ee = ve[k];
            el = vl[temp->end] - temp->weight;
            if (ee == el) {//這兩個值相等,說明它為關鍵活動:
                cout << this->arc[k].data
                    << "----"
                    << this->arc[temp->end].data
                    << "="
                    << temp->weight
                    << endl;
            }
            temp = temp->next;
        }
    }
}
  • main.cpp檔案的程式碼
#include"CriticalPath.h"

//檢驗輸入邊數和頂點數的值是否有效,可以自己推算為啥:
//頂點數和邊數的關係是:((Vexnum*(Vexnum - 1)) / 2) < edge
bool check(int Vexnum, int edge) {
    if (Vexnum <= 0 || edge <= 0 || ((Vexnum*(Vexnum - 1)) / 2) < edge)
        return false;
    return true;
}
int main() {
    int vexnum; int edge;

    cout << "輸入圖的頂點個數和邊的條數:" << endl;
    cin >> vexnum >> edge;
    while (!check(vexnum, edge)) {
        cout << "輸入的數值不合法,請重新輸入" << endl;
        cin >> vexnum >> edge;
    }
    Graph_DG graph(vexnum, edge);
    graph.createGraph();
    graph.print();
    graph.CriticalPath();
    system("pause");
    return 0;

}

構造下圖: 這裡寫圖片描述

輸入:

9 11
1 2 6
1 3 4
1 4 5
2 5 1
3 5 1
4 6 2
5 7 9
5 8 7
6 8 4
7 9 2
8 9 4

輸出: 這裡寫圖片描述

從輸出可以看出,這個圖有兩條關鍵路徑: 第一條就是:

v1--v2--v5--v7--v9
  • 1

第二條是:

v1--v2--v5--v8--v9
  • 1

我們在構造下圖的關鍵路徑: 這裡寫圖片描述

輸入:

6 8
1 2 3
1 3 2
2 4 2
2 5 3
3 4 4
3 6 3
4 6 2
5 6 1

輸出: 這裡寫圖片描述

從輸出可以看出,這個圖有一條關鍵路徑:

v1--v3--v4--v6
  • 1
					<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-8cccb36679.css" rel="stylesheet">
            </div>
	
</article>