圖論(4):關(guān)鍵路徑概念與算法(Graph實(shí)現(xiàn))

概念

AOE網(wǎng)對(duì)應(yīng)研究實(shí)際問題是工程的工期問題:(1)完成一項(xiàng)工程至少需要多少時(shí)間?(2)哪些活動(dòng)是影響整個(gè)工程進(jìn)度的關(guān)鍵?

如果在有向圖中用頂點(diǎn)表示事件,用弧表示活動(dòng),用弧上的權(quán)表示活動(dòng)持續(xù)時(shí)間,則稱該帶權(quán)有向圖(即有向網(wǎng))為邊表示活動(dòng)的網(wǎng)(activity on edge network),簡稱AOE網(wǎng)。如下圖所示:

AOE網(wǎng)-活動(dòng)與事件關(guān)系圖

AOE網(wǎng)中的事件與活動(dòng)有如下兩個(gè)重要性質(zhì):
1、只有在某頂點(diǎn)所代表的事件發(fā)生后,從該頂點(diǎn)出發(fā)的各活動(dòng)才能開始;
2、只有在進(jìn)入某頂點(diǎn)的各活動(dòng)都結(jié)束,該頂點(diǎn)所代表的事件才能發(fā)生。

由于一個(gè)工程只可能有一個(gè)開始點(diǎn)和一個(gè)完成點(diǎn),故正常情況(無環(huán))下,網(wǎng)中只可能有一個(gè)入度為0的節(jié)點(diǎn)(稱為源點(diǎn))和一個(gè)出度為0的節(jié)點(diǎn)(稱為匯點(diǎn))。

通常在一個(gè)工程中一些活動(dòng)可以并行地進(jìn)行,另一些活動(dòng)則需要等待前面所有的活動(dòng)完成后才能開始(因此,這里還涉及到事件的拓?fù)渑判颍酝瓿烧麄€(gè)工程的最短時(shí)間應(yīng)該是從開始點(diǎn)到結(jié)束點(diǎn)的最長路徑的長度(指的是活動(dòng)持續(xù)時(shí)間之和,而非弧的數(shù)目)。路徑長度最長的那條路徑叫做關(guān)鍵路徑,關(guān)鍵路徑上的活動(dòng)稱為關(guān)鍵活動(dòng)。因此,提前完成非關(guān)鍵活動(dòng)并不能加快工程的進(jìn)度。

與關(guān)鍵活動(dòng)有關(guān)的量

如果我們用e(i)標(biāo)識(shí)活動(dòng)a(i)的最早開始時(shí)間,l(i)表示活動(dòng)a(i)的最遲開始時(shí)間(這是在不推遲整個(gè)工程完成的前提下活動(dòng)a(i)的最遲必須開始的時(shí)間),兩者之差l(i) - e(i)意味著活動(dòng)a(i)的時(shí)間余量。僅當(dāng)活動(dòng)a(i)滿足條件l(i) == e(i)(即活動(dòng)的時(shí)間余量為0)時(shí)表示為關(guān)鍵活動(dòng)

因此,要獲得工程的關(guān)鍵路徑就是找出滿足條件l(i) == e(i)的所有活動(dòng)(一個(gè)工程中可能存在多條關(guān)鍵路徑)。

為了求得活動(dòng)的e(i)和l(i),首先應(yīng)求得事件的最早發(fā)生時(shí)間ve(j),和最遲發(fā)生時(shí)間vl(j)。如果活動(dòng)a(i)由弧<j, k>表示,其持續(xù)時(shí)間記為dut(<j, k>),則滿足如下關(guān)系式:
e(i) = ve(j)
l(i) = vl(k) - dut(<j, k>)

事件的最早發(fā)生時(shí)間ve(j):是指從始點(diǎn)開始到頂點(diǎn)(事件)j的最大路徑長度。這個(gè)長度決定了所有從頂點(diǎn)j出發(fā)的活動(dòng)能夠開工的最早時(shí)間。
從開始點(diǎn)往后遞推求取:
ve(0) = 0; ve(j) = Max{ve(i) + dut(<i, j>) }, 其中<i, j>∈T, j = 1, 2, ..., n -1; T為節(jié)點(diǎn)j的入度邊集合。

事件最遲發(fā)生時(shí)間vl(j):是指在不推遲整個(gè)工期的前提下,事件j允許的最晚發(fā)生時(shí)間。
從結(jié)束點(diǎn)往前遞推求取:
vl(n - 1) = ve(n - 1); vl(i) = Min{vl(j) - dut(<i, j>)},其中<i, j>∈S, i = n - 2, ..., 0; S為節(jié)點(diǎn)i的出度邊集合。

示例

利用Guava的graph數(shù)據(jù)結(jié)構(gòu)求得如下所示工程圖的關(guān)鍵路徑。graph的使用相關(guān)介紹請(qǐng)參考:圖論(2):Guava中Graph模塊(wiki翻譯)

關(guān)鍵路徑示例圖

1、構(gòu)建示例圖AOE網(wǎng)的數(shù)據(jù)結(jié)構(gòu):

MutableValueGraph<String, Integer> graph = ValueGraphBuilder.directed()
    .nodeOrder(ElementOrder.insertion())
    .expectedNodeCount(10)
    .build();

graph.putEdgeValue(V1, V2, 6);
graph.putEdgeValue(V1, V3, 4);
graph.putEdgeValue(V1, V4, 5);
graph.putEdgeValue(V2, V5, 1);
graph.putEdgeValue(V3, V5, 1);
graph.putEdgeValue(V4, V6, 2);
graph.putEdgeValue(V5, V7, 9);
graph.putEdgeValue(V5, V8, 7);
graph.putEdgeValue(V6, V8, 4);
graph.putEdgeValue(V7, V9, 2);
graph.putEdgeValue(V8, V9, 4);
Log.i(TAG, "graph: " + graph);

輸出:

nodes: [v1, v2, v3, v4, v5, v6, v7, v8, v9], 
edges: {<v1 -> v4>=5, <v1 -> v2>=6, <v1 -> v3>=4, 
<v2 -> v5>=1, <v3 -> v5>=1, <v4 -> v6>=2, <v5 -> v8>=7, 
<v5 -> v7>=9, <v6 -> v8>=4, <v7 -> v9>=2, <v8 -> v9>=4}

2、獲取該有向圖的拓?fù)渑判蛄斜恚?/p>

/**
 * 利用Traverser接口將graph進(jìn)行拓?fù)渑判騮opologically,此處返回的逆拓?fù)渑判? */
Iterable<String> topologicallys = Traverser.forGraph(graph)
        .depthFirstPostOrder(startNode);
Log.i(TAG, "topologically: " + format(topologicallys));

輸出:

topologically: {v9,v8,v6,v4,v7,v5,v2,v3,v1} //這里是逆序

3、遞推求得ve(j)值:


//獲取ve(i)
Map<String, Integer> ves = getVeValues(graph, topologicallys);
Log.i(TAG, "ves: " + format(ves));

/**
 * ve(j) = Max{ve(i) + dut(<i,j>) }; <i,j>屬于T,j=1,2...,n-1
 * @param graph
 * @param topologicallys
 * @return
 */
private static Map<String, Integer> getVeValues(ValueGraph<String, Integer> graph, 
                                                Iterable<String> topologicallys) {
    List<String> reverses = Lists.newArrayList(topologicallys.iterator());
    Collections.reverse(reverses); //將逆拓?fù)渑判蚍聪?    Map<String, Integer> ves = new ArrayMap<>(); //結(jié)果集
    //從前往后遍歷
    for (String node : reverses) {
        ves.put(node, 0); //每個(gè)節(jié)點(diǎn)的ve值初始為0

        //獲取node的前趨列表
        Set<String> predecessors = graph.predecessors(node); 
        int maxValue = 0;
        
        //找前趨節(jié)點(diǎn)+當(dāng)前活動(dòng)耗時(shí)最大的值為當(dāng)前節(jié)點(diǎn)的ve值
        for (String predecessor : predecessors) {
            maxValue = Math.max(ves.get(predecessor) +
                    graph.edgeValueOrDefault(predecessor, node, 0), maxValue);
        }
        ves.put(node, maxValue);
    }
    return ves;
}

輸出:

ves: {v1:0, v2:6, v3:4, v4:5, v5:7, v6:7, v7:16, v8:14, v9:18}

4、遞推求得vl(j)值:



/**
 * vl(i) = Min{vl(j) - dut(<i,j>}; <i,j>屬于S,i=n-2,...,0
 * @param graph
 * @param topologicallys
 * @param vels
 * @return
 */
private static Map<String, Integer> getVlValues(ValueGraph<String, Integer> graph,
    Iterable<String> topologicallys, Map<String, Integer> vels) {
    Map<String, Integer> vls = new ArrayMap<>(); //結(jié)果集
    //從后往前遍歷
    for (String node : topologicallys) {
        //獲取node的后繼列表
        Set<String> successors = graph.successors(node);
        int initValue = Integer.MAX_VALUE; //初始值為最大值
        if (successors.size() <= 0) { //表示是結(jié)束點(diǎn),賦值為ve值
            initValue = vels.get(node);
        }
        vls.put(node, initValue);
        int minValue = initValue;
        //找后繼節(jié)點(diǎn)-當(dāng)前活動(dòng)耗時(shí)最少的值為當(dāng)前節(jié)點(diǎn)的vl值
        for (String successor : successors) {
            minValue = Math.min(vls.get(successor) -
                    graph.edgeValueOrDefault(node, successor, 0), minValue);
        }
        vls.put(node, minValue);
    }
    return vls;
}

輸出:

vls: {v1:0, v2:6, v3:6, v4:8, v5:7, v6:10, v7:16, v8:14, v9:18}

5、根據(jù)前面求取的ve(j)和vl(j)來找出關(guān)鍵活動(dòng)(判斷條件:ve(j) == vl(k) - dut(<j,k>)):

/**
 * 判斷條件:ve(j) == vl(k) - dut(<j,k>)
 */
//關(guān)鍵活動(dòng)結(jié)果集
List<EndpointPair<String>> criticalActives = new ArrayList<>(); 
//返回圖中所有活動(dòng)(邊)
Set<EndpointPair<String>> edgs = graph.edges(); 
//遍歷每一條邊(活動(dòng)),過濾出:ve(j) == vl(k) - dut<j, k>
for (EndpointPair<String> endpoint : edgs) {
    final int dut = graph.edgeValueOrDefault(endpoint.nodeU(), endpoint.nodeV(), 0);
    //ve(j) == vl(k) - dut<j, k>
    if (vls.get(endpoint.nodeV()) - dut == ves.get(endpoint.nodeU())) { 
        criticalActives.add(endpoint);
    }
}
Log.i(TAG, "critical actives: " + format(criticalActives));

輸出:

critical actives: {<v1 -> v2>, <v2 -> v5>, <v5 -> v8>, <v5 -> v7>,
<v7 -> v9>, <v8 -> v9>}

從輸出可知,圖中存在兩條關(guān)鍵路徑:{<v1 -> v2>, <v2 -> v5>, <v5 -> v8>, <v8 -> v9>} 和 {<v1 -> v2>, <v2 -> v5>, <v5 -> v7>, <v8 -> v9>}(在示例圖中使用紅色線段標(biāo)注)。因此,縮短這兩條路徑上活動(dòng)的工期,將能有效的縮短整個(gè)工程的工期。
關(guān)鍵路徑詳細(xì)代碼參見Demo:GH-Demo-CriticalPath

參考文檔

https://www.cnblogs.com/hongyang/p/3407666.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。