算法與數據結構(八) AOV網的關鍵路徑(Swift版)

上篇博客咱們介紹了AOV網的拓撲序列,請參考《數據結構(七) AOV網的拓撲排序(Swift面向對象版)》。拓撲序列中包括項目的每一個結點,沿着拓撲序列將項目進行下去是確定能夠將項目完成的,可是工期不是最優的。由於拓撲序列是一個串行序列,若是按照該序列執行項目,那麼就是串行執行的。咱們知道在一個項目中的一些子工程是能夠並行來完成的,這也就相似咱們的多線程。今天咱們要解決的問題就是找出一個關鍵路徑,是工期最優並保證工程的完成。什麼是關鍵路徑,咱們在下方會進行詳細介紹。html

 

1、關鍵路徑概述git

在聊關鍵路徑以前,咱們先看一個簡單的實例,以下圖所示。咱們將下方這個有向無環圖看作是整個工程,將每一個節點看作是該項目工程的一個子工程。子工程間又有必定的優先級。在下方圖中,A的優先級最高。A作完後,B、C才能夠進行開發。B、C完成後,咱們才能夠開發D。從下圖中咱們不難看出,該圖的拓撲序列爲A, B, C, D。若是咱們按照串行的方式來完成此工程的話,那麼工程完成的順序能夠是A-5->B, A-8->C, B-3->D, C-10->D。總時間爲26github

從上面這個序列中咱們顯然能夠看出來這不是最優的,由於A->B, A->C能夠同時進行B->D和C->D也能夠同時進行。在容許某些子工程同時進行的狀況下,A->B和A-C能夠同時進行,由於A->B所需時間小於A->C所需時間,因此咱們選擇A->C。在A->C執行這8個小時的時間裏,A->B和B->D已經執行完了,就剩下C->D了,因此關鍵工期爲A->C->D=18算法

在求關鍵路徑的算法中,咱們先求出每一個事件的最先完成時間。在事件最先完成的時間集合中,工程最後一步完成的時間就是咱們工程完成的最優時間。而後在工程時間最優的狀況下求出每一個事件最晚完成時間。若是某個時間最先的完成時間與最晚的完成時間相同,那麼該事件就是咱們的關鍵事件,該事件就位於咱們關鍵路徑中。若是這樣敘述有些抽象,那麼咱們就拿下方這個簡單圖來作個類比。數組

在上方這個有向無環圖中,咱們能夠求出每一個事件最先發生的時間。下方截圖就是每一個事件所對應的最先完成的事件,由於D是工程的尾結點,因此該工程完成的最先時間也就是D完成的最先時間,即工程完成的最先時間爲18。數據結構

  

在整個工程最先完成的時間下,咱們能夠從後往前推出每一個子工程最晚的完成時間。這最晚完成時間就是在不耽誤整個工程最小工期的前提下,最晚的完成時間。每一個工程的最晚完成時間咱們能夠倒着推出。也就是從D=18日後推出。下方就是每一個工程在保證整個工期是的完成時間是18的前提下的最晚完成時間。多線程

  

對比上了最先完成時間和最晚完成時間,咱們能夠看出A, C, D這三個結點的最先完成時間與最晚完成時間相同,因此是咱們的關鍵結點。這幾個結點鏈接的路徑就是咱們的關鍵路徑。因此上圖中的關鍵路徑就是A->C->D函數

 

2、關鍵路徑算法的具體步驟post

第一部分由於示例比較簡單,算是咱們本篇博客的開胃小菜,接下來進入咱們本篇博客真正的主題。在本部分,咱們仍是以原理圖爲主,本部分不會給出具體的代碼實現,咱們只講原理。本篇博客是在上篇博客的基礎上實現的,由於每一個路徑的最先執行的時間的計算依賴於拓撲序列,因此咱們依然會採用上篇博客咱們拓撲序列所使用的圖的結構。下方就是咱們要求關鍵路徑的有向無環圖。若是你看過前幾天博客的話,那麼對下方這個圖的結構應該是很是熟悉了吧,今天咱們依然會使用下方這個圖來作咱們的實例。測試

  

1.最先完成時間計算

首先咱們根據拓撲排序的過程來計算出每一個結點最先完成時間。最先完成時間計算的計算過程就是在拓撲排序的過程當中添加一段記錄每一個結點完成的最先時間,下方就是求最先完成時間的整個實例圖,下方會給出每一步的詳細介紹。下方是由拓撲排序計算最先完成時間的具體步驟,而且給出了每一步的計算規則。

其實下方這個步驟與上一篇博客中拓撲排序的步驟大同小異,只是在其基礎上引入了一個數組。數組中記錄的就是索引對應結點的最先完成時間,具體步驟以下所示。下方的每一步其實就是拓撲排序的步驟,只是加入了每一個結點最先完成時間的計算,由於上篇博客對拓撲排序作了詳細的敘述,在此就不作過多贅述了。

  • (1)、首先咱們先建立好一個數組用於存儲每一個結點最先完成的時間,數組元素的初始值爲零,由於其實結點的完成時間就是0。
  • (2)、這一步中,結點A加入了拓撲序列,因此咱們能夠計算出與A結點相連結點的完成時間。由於A-10->B, A的完成時間爲0,0+10,因此B的此刻的完成時間爲10,同理咱們能夠求出F的此刻完成時間是11
  • (3)、在第三步中,F進入拓撲序列,與F結點相連的結點是G和E。因此咱們能夠由F的此刻的完成時間11和F->G所需完成的時間17,求出G的此刻的完成時間11+17=28。同理咱們能夠求出結點E的此刻的完成時間爲11+26 = 37
  • (4)、本步驟中E結點進入拓撲序列。由上面一步咱們知道E的完成時間是37,那麼不可貴出與E相連的結點D目前的完成時間是37+20 = 57。同理,H結點目前的完成時間爲37+7=44
  • (5)、該步驟中B結點進入拓撲序列,與B相連的結點有B-16->G, B-12->I, B-18->C。咱們先來從B到G這條路徑中G的完成時間,由B->G的完成時間爲10+16=26。咱們以前求的F->G這條路徑中G的完成時間是28,由於B和F都是G的前提,B和F有一個完不成,G就沒法提早完成,因此咱們選擇F->G這條路徑來做爲G的完成時間,由於FG(28)>BG(26), 低於28這個時間,G就完不成,因此此輪不更新G的完成時間。同理,此輪咱們求出I的完成時間爲10+12=22C的完成時間爲10+18=28
  • (6)、本步驟中C進入拓撲序列,由C-8->I可求出C路徑中I的完成時間是28+8=36,與以前求得B->I路徑中I的完成時間22相比要大,因此更新I的完成時間爲36。由C-22->D可求出該路徑中D的完成時間爲28+22=50,而上面咱們計算的D的完成時間爲57,50<57, 此輪不更新D的完成時間。
  • (7)、I進入拓撲序列,由I-21->D,可知,D在該路徑中的完成時間爲36+21=57。與以前D的完成時間相等,此輪也不更新D的完成時間。
  • (8)、G進入拓撲序列。有G-19->H能夠求出,G路徑上的H的完成時間爲28+19=47。咱們以前計算的H的完成時間爲44,因此本輪將H的完成時間更新爲47。
  • (9)、H進入拓撲序列。有H-16->D能夠求出本輪D的完成時間爲47+16=63。63大於上面計算的D的完成時間57,因此將D的完成時間更新爲63。
  • (10)、D進入拓撲序列,由於D爲終點,因此拓撲排序結束,咱們最先完成時間也計算完畢。

  

    

通過上面這些步驟,上面數組中所存儲的就是每一個結點的最先完成時間,以下所示:

  

2.計算最遲完成時間

上面由拓撲排序從前日後計算的完成時間就是咱們每一個結點的最先完成時間,接下來咱們將要計算每一個結點在總時間不變的狀況下,最晚完成時間。每一個結點的最晚完成時間咱們要從後往前計算,由於整工程的總時間肯定,從後往前咱們就能夠計算每一個結點最晚完成時間。下方就是計算最晚完成時間的全部的詳細步驟。

由於咱們是按照拓撲排序的序列從後往前計算的最晚完成時間,因此咱們將拓撲序列從頭至尾依次進入棧。而後以出棧的順序來計算最晚完成時間,此刻的出棧順序就是拓撲排序的逆序。因此下方計算每一個結點的最晚完成時間時要藉助棧的數據結構來完成。和上述計算最先完成時間相似,依然是將完成時間存入數組中,而後根據咱們計算的數據進行更新完成時間。

下方是對最晚完成時間示例圖的詳細介紹:

  • (1):首先初始化咱們存儲最晚完成時間的數組,由於整個工程的完成時間時63,因此咱們初始化每一個結點的最晚完成時間就以63爲準。由於再晚也不會超過63。在該步驟中,咱們將D出棧。由於D是最後一個完成的結點, 因此其最晚完成時間就是63,咱們不作任何的更新。
  • (2):接着咱們將H出棧,有 H-16->D可知,H此刻的完成時間爲 63-16= 47,更新H對應的完成時間。
  • (3):將G出棧,由 G-19->H能夠計算出GH這條路徑中G的完成時間爲47-19=28,由 G-24->D這條路徑能夠計算出GD這條路徑中G的完成時間爲63-24=39。由於28<39, 爲不耽誤H的正常進行, 因此G此刻的最晚完成時間爲28
  • (4):將I出棧,由 I-21->D這條路徑,咱們能夠計算出, 此刻I的最晚完成時間爲63-21=42
  • (5):將C出棧,由 C-8->I能夠計算出C在CI這條路徑中最晚完成時間爲42-8=34,由 C-22->D這條路徑能夠計算出CD這條路徑中C的最晚完成時間爲 63-22=41。由於34<41, 爲了避免耽誤I的完成,因此C的最晚完成時間爲34
  • (6):將B出棧:有 B-16->G能夠計算出,BG這條路徑的最晚完成時間爲 28-16=12,同理可計算出BI這條路徑中B的最晚完成時間爲42-12=30,BC這條路徑中B的最晚完成時間爲34-18=12。 因此B的最晚完成時間爲12
  • (7):將E出棧,由 E-20->D中能夠求出ED這條路徑中E的最晚完成時間爲63-20=43,同理可求出EH這條路徑中E的最晚完成時間爲47-7=40,最有E的最晚完成時間爲40.
  • (8):將F出棧,由F-26->E可求出FE這條路徑中F的最晚完成時間爲 40-26=14,有F-17->G這條路徑中能夠求出FG這條路徑中F的最晚完成時間爲28-17=11,因此F的最晚完成時間爲11。
  • (9):將A出棧,由A-10->B能夠求出,AB這條路徑中A的最晚完成時間爲 12-10=2,同理AF這條路徑中A的最晚完成時間爲11-11=0,因此A的最晚完成時間爲0。至此棧中的元素爲空,咱們的最晚完成時間就計算完畢了,示例圖以下所示。

  

  

通過上述步驟咱們就能夠計算出每一個結點的最晚完成時間,以下所示:

  

 

3.計算關鍵路徑

由每一個結點的最先完成時間和最晚完成時間咱們就能夠計算出咱們的關鍵路徑了。由於工程的總時間是固定的,那些最先完成時間等於最晚完成時間的結點就是咱們所要找的關鍵結點。下方就是在圖遍歷時,根據最先完成時間和最晚完成時間的對比,求出關鍵路徑具體步驟。

  • (1):從最先和最晚完成時間中咱們能夠看出來關鍵結點有 A, D, F, G, H。咱們能夠在遍歷圖時給出這幾個結點的前後順序。
  • (2):從A結點開始遍歷,A與F,B相連,F的最晚時間可最先完成時間相等,因此發展成關鍵路徑, A-11->F
  • (3):F與E和G相連,G的最晚和最先完成時間等,因此此刻的關鍵路徑爲 A-11->F-17->G
  • (4):G與D和H相連,G的最晚時間是47-19=28獲得的,因此此刻的關鍵路徑爲 A-11->F-17->G-17->H
  • (5):以此類推,能夠計算出關鍵路徑爲 A-11->F-17->G-17->H-16->D

 

  

 

3、關鍵路徑的代碼實現

上面給出了關鍵路徑的詳細求解步驟,若是你將上面每一個步驟搞明白後,給出代碼實現並不難。接下來咱們就會根據上面的步驟給出具體的代碼實現。固然咱們依然使用Swift語言實現,固然使用的是當前Swift最新版本,也就Swift3.0

從上面的步驟中咱們能夠大致分爲三步:

  • 第一步:根據拓撲序列求出每一個結點最先完成時間。
  • 第二步:根據拓撲的逆序列,結合着最先完成時間求出每一個結點的最晚完成時間。
  • 第三步:結合着最先完成時間和最晚完成時間,根據圖的結構求出關鍵路徑。

接下來咱們的代碼實現也是根據上面這三步來實現的。進入咱們代碼實現的部分。

 

1.計算最先完成時間

本部分代碼與上篇博客中拓撲排序的代碼差很少,就多了下方紅框中的部分。下方多出的代碼就是在拓撲排序的過程當中求出每一個結點的最先完成時間,而後存儲在earliestTimeOfVertex數組中。由於代碼與拓撲排序的代碼相似,因此在此就不作過多贅述了。

  

 

2.計算最晚完成時間

計算爲最先完成時間後,咱們工程的整個工期也就是定了。根據這個固定的工期,而後結合着拓撲排序的倒序,就可求出每一個結點最晚完成的時間。下方這段代碼就是計算每一個結點的最晚完成時間。就是從後往前計算。

首先將拓撲序列入棧,也就是將拓撲序列逆序的一個過程。而後不斷從棧中取值,取一個結點就要計算該結點的最晚完成時間。與該結點相連結點的最晚時間 - 權值= 該結點的最晚完成時間。在這個過程當中取最小的哪一個時間,就是當前結點最晚完成的時間。具體代碼以下所示:

 

3.計算關鍵路徑

上面兩步計算完最先完成時間和最晚完成時間後,接下來咱們就要開始計算咱們的關鍵路徑了。下方代碼其實就是在圖的層次遍歷時,查找那些最先完成時間與最晚完成時間相等的結點,若是相等,則是關鍵路徑上的結點,而後將該節點進行輸出。

固然下方代碼中if後方的等式是個關鍵,將該等式翻譯成文字就是:結點最先完成時間 == 下一個結點的最晚完成時間 - 該節點到下一個結點的權值 == 該結點最晚完成時間,若是上面這個等式成立,那麼就說明該結點是關鍵結點,咱們將其進行輸出。具體代碼以下所示。

  

 

4.測試用例

上面三步是關鍵路徑計算的全部代碼,接下來又到了咱們測試的時刻了。下方就是咱們的測試用例,首先咱們根據圖的結點和關係建立有向圖。而後輸出咱們建立的這個有向無環圖。爲了清晰的能看出每一步的執行,咱們並無將三步封裝成一個函數來調用。下方的第一步就是求最先完成時間,第二步就是計算最晚完成時間,第三步就是計算咱們的關鍵路徑了。

  

下方就是咱們的測試用例的輸出結果了,輸出結果仍是比較直觀的,有圖有真相,在此就不作過多贅述了。

  

 

好今天的博客就到這兒,下幾篇博客依然是關於數據結構的,敬請期待。今天博客中的Demo依然會在github上進行分享。下方是分享地址。

github分享地址:https://github.com/lizelu/DataStruct-Swift/tree/master/CriticalPath

相關文章
相關標籤/搜索