最短路徑算法及應用

轉載自:http://blog.csdn.net/baggioan/article/details/1713294

乘汽車旅行的人總希望找出到目的地的儘可能的短的行程。如果有一張地圖並在圖上標出每對十字路口之間的距離,如何找出這一最短行程?

  一種可能的方法就是枚舉出所有路徑,並計算出每條路徑的長度,然後選擇最短的一條。那麼我們很容易看到,即使不考慮包含迴路的路徑,依然存在數以百萬計的行車路線,而其中絕大多數是不值得考慮的。

  在這一章中,我們將闡明如何有效地解決這類問題。在最短路徑問題中,給出的是一有向加權圖G=(V,E,W),其中V爲頂點集,E爲有向邊集,W爲邊上的權集。最短路徑問題研究的問題主要有:單源最短路徑問題、與所有頂點對之間的最短路徑問題。 

  一、 單源最短路徑問題

  所謂單源最短路徑問題是指:已知圖G=(V,E),我們希望找出從某給定的源結點S∈V到V中的每個結點的最短路徑。

  首先,我們可以發現有這樣一個事實:如果P是G中從vs到vj的最短路,vi是P中的一個點,那麼,從vs沿P到vi的路是從vs到vi的最短路。

  (一)Dijkstra算法

  對於圖G,如果所有Wij≥0的情形下,目前公認的最好的方法是由Dijkstra於1959年提出來的。
  例1 已知如下圖所示的單行線交通網,每弧旁的數字表示通過這條單行線所需要的費用,現在某人要從v1出發,通過這個交通網到v8去,求使總費用最小的旅行路線。

          500){this.resized=true;this.style.width=500;}">

  Dijkstra方法的基本思想是從vs出發,逐步地向外探尋最短路。執行過程中,與每個點對應,記錄下一個數(稱爲這個點的標號),它或者表示從vs到該點的最短路的權(稱爲P標號)、或者是從vs到該點的最短路的權的上界(稱爲T標號),方法的每一步是去修改T標號,並且把某一個具T標號的改變爲具P標號的點,從而使G中具P標號的頂點數多一個,這樣至多經過n-1(n爲圖G的頂點數)步,就可以求出從vs到各點的最短路。

  在敘述Dijkstra方法的具體步驟之前,以例1爲例說明一下這個方法的基本思想。例1中,s=1。因爲所有Wij≥0,故有d(v1, v1)=0。這時,v1是具P標號的點。現在考察從v1發出的三條弧,(v1, v2), (v1, v3)和(v1, v4)。如果某人從v1出發沿(v1, v2)到達v2,這時需要d(v1, v1)+w12=6單位的費用;如果他從v1出發沿(v1, v3)到達v3,這時需要d(v1, v1)+w13=3單位的費用;類似地,若沿(v1, v4)到達v4,這時需要d(v1, v1)+w14=1單位的費用。因爲min{ d(v1, v1)+w12,d(v1, v1)+w13,d(v1, v1)+w14}= d(v1, v1)+w14=1,可以斷言,他從v1到v4所需要的最小費用必定是1單位,即從v1到v4的最短路是(v1, v4),d(v1, v4)=1。這是因爲從v1到v4的任一條路P,如果不是(v1, v4),則必是先從v1沿(v1, v2)到達v2,或者沿(v1, v3)到達v3。但如上所說,這時他已需要6單位或3單位的費用 ,不管他如何再從v2或從v3到達v4,所需要的總費用都不會比1小(因爲所有wij≥0)。因而推知d(v1, v4)=1,這樣就可以使v4變成具P標號的點。現在考察從v1及v4指向其餘點的弧,由上已知,從v1出發,分別沿(v1, v2)、(v1, v3)到達v2, v3,需要的費用分別爲6與3,而從v4出發沿(v4, v6)到達v6所需的費用是d(v1, v4)+w46=1+10=11單位。因min{ d(v1, v1)+w12,d(v1, v1)+w13,d(v1, v4)+w46}= d(v1, v1)+w13=3。基於同樣的理由可以斷言,從v1到v3的最短路是(v1, v3),d(v1, v3)=3。這樣又可以使點v3變成具P標號的點,如此重複這個過程,可以求出從v1到任一點的最短路。

  在下述的Dijstra方法具體步驟中,用P,T分別表示某個點的P標號、T標號,si表示第i步時,具P標號點的集合。爲了在求出從vs到各點的距離的同時,也求出從Vs到各點的最短路,給每個點v以一個λ值,算法終止時λ(v)=m,表示在Vs到v的最短路上,v的前一個點是Vm;如果λ(v)=∞,表示圖G中不含從Vs到v的路;λ(Vs)=0。
Dijstra方法的具體步驟:

  {初始化}
  i=0
  S0={Vs},P(Vs)=0 λ(Vs)=0
  對每一個v<>Vs,令T(v)=+ ∞,λ(v)=+ ∞,
  k=s
  {開始}
  ①如果Si=V,算法終止,這時,每個v∈Si,d(Vs,v)=P(v);否則轉入②
  ②考察每個使(Vk,vj)∈E且vj Si的點vj。
  如果T(vj)>P(vk)+wkj,則把T(vj)修改爲P(vk)+wkj,把λ(vj)修改爲k。
  ③令500){this.resized=true;this.style.width=500;}" align=middle> 
  如果500){this.resized=true;this.style.width=500;}" align=middle>,則把500){this.resized=true;this.style.width=500;}" align=middle>的標號變爲P標號500){this.resized=true;this.style.width=500;}" align=middle>,令500){this.resized=true;this.style.width=500;}" align=middle> ,k=ji,i=i+1,轉①,否則終止,這時對每一個v∈Si,d(vs,v)=P(v),而對每一個500){this.resized=true;this.style.width=500;}" align=middle> 。

  算法實現:

  1、 數據結構

  * 用鄰接矩陣表示圖G
  * 用一維數組D[I]存放從源點到I點的最短路徑長度或其上界。(上面算法中的P標號與T標號實際上存放着源點到某點的最短路徑長度或其上界,因此我們可以統一用D數組來表示)。
  * 用一維數組P,其中P[I]記錄到I點的最短路徑中前一個頂點的序號。
{$R+}

  2、源程序

  500){this.resized=true;this.style.width=500;}" resized="true">
  500){this.resized=true;this.style.width=500;}">
 500){this.resized=true;this.style.width=500;}" resized="true">
 500){this.resized=true;this.style.width=500;}">
 500){this.resized=true;this.style.width=500;}">

  (二)Bellman-Ford算法

  在單源最短路徑問題的某些實例中,可能存在權爲負的邊。如果圖G=(V,E)不包含從源s可達的負權迴路,則對所有v∈V,最短路徑的權定義d(s,v)依然正確,即使它是一個負值也是如此。但如果存在一從s可達的負迴路,最短路徑的權的定義就不能成立。S到該回路上的結點就不存在最短路徑。當有向圖中出現負權時,則Dijkstra算法失效。當不存在源s可達的負迴路時,我們可用Bellman-Ford算法實現。

  下面我們介紹有向圖中,存在具有負權的弧時,求最短路的方法。
  爲了方便起見,不妨設從任一點vi到任一點vj都有一條弧(如果在Gk ,(vi,vj)不存在,則添加(vi,vj)且令wij=+∝)。

  顯然,從vs到vj的最短路總是從vs出發,沿着一條路到某個點vi,再沿(vi,vj)到vj的(這裏vi可以是vs本身),由本章開始時介紹的一個結論可知,從vs到vi的這條路必定是從vs到vi的最短路,所以d(vs,vi)必滿足如下方程:
        500){this.resized=true;this.style.width=500;}" align=middle>

  爲了求得這個方程的解500){this.resized=true;this.style.width=500;}" align=middle> (這裏P爲圖G中的頂點數目),可用如下遞推公式:
  開始時,令
        500){this.resized=true;this.style.width=500;}" align=middle>
  對t=2,3,...,
        500){this.resized=true;this.style.width=500;}" align=middle>
         
  若進行到某一步,例如第k步時,對所有j=1,2,...,p,有:
        500){this.resized=true;this.style.width=500;}" align=middle>
500){this.resized=true;this.style.width=500;}" align=middle> 即爲vs到各點的最短路的權。

  不難證明:
  (1)如果G是不含迴路的賦權有向圖,那麼,從vs到任一個點的最短路必可取爲初等路,從而最多包含P-2箇中間點;
  (2)上述遞推公式中的 500){this.resized=true;this.style.width=500;}" align=middle>是在至多包含t-1箇中間點的限制條件下,從vs到vj的最短路的權。

  由(1)(2)可知:當G中不含負迴路時,上述算法最多經過p-1次迭代必定收斂,即對所有的j=1,2,...,P,均有500){this.resized=true;this.style.width=500;}" align=middle> ,從而求出從vs到各個頂點的最短路的權。
  如果經過p-1次迭代,存在某個j,使500){this.resized=true;this.style.width=500;}" align=middle> ,則說明G中包含有負迴路。顯然,這時從vs到vj的路是沒有下界的。
  根據以上分析,Bellman-Ford算法可描述爲:

  500){this.resized=true;this.style.width=500;}" align=middle resized="true">

  算法實現

  1、 數據結構(同Dijkstra算法,略)
  2、源程序
  
  500){this.resized=true;this.style.width=500;}" align=middle> 
   500){this.resized=true;this.style.width=500;}" align=middle resized="true"> 
   500){this.resized=true;this.style.width=500;}" align=middle> 
   500){this.resized=true;this.style.width=500;}" align=middle> 

  (三)有向無迴路圖中的單源最短路徑

  若圖G是一個無迴路有向圖,求圖G的單源最短路徑問題可以在O(V+E)時間內計算出單源最短路徑。在有向無迴路圖中最短路徑總是存在的,這是因爲即使圖中有權爲負的邊,也不可能存在權爲負的迴路。

  算法開始時先對有向無迴路圖進行拓樸排序,以便獲得結點的線性序列。如果從結點u到結點v存在一通路,則在拓撲序列中u先於v。在拓撲排序過程中我們僅對結點執行一趟操作。當對每個結點進行處理時,從該結點出發的所有邊也被鬆馳。

  算法描述如下:

 500){this.resized=true;this.style.width=500;}" align=middle>

  二、每對結點間的最短路徑

  我們要討論找出圖中每對結點間最短路徑問題。這個問題在實踐中常常會出現。例如,對一公路圖,要造表說明其上的每對城市間的距離時就可能出現這種問題。

  對於有向圖G(V,E,W),要求每對結點間的最短路徑,我們可以把單源最短路徑算法運行|V|次來解決,每次依序把圖中的每個結點作爲源點。如果所有邊的權爲非負,可以採用Dijkstra算法,算法的運行時間爲O(V3);如果允許有負權邊的存在,我們必須對每個結點運行一次速度較慢的Bellman-Ford算法,其中運行時間爲O(V2E),對稠密圖則爲O(V4)。

  下面我們介紹一種動態程序設計方案來解決可以存在負權邊但無負迴路的有向圖G=(V,E),每對結點間的最短路徑問題,所產生的算法稱爲Floyd-Warshall算法,其運行時間爲O(V3)。

  (一)最短路徑的結構

  在Floyd-Warshall算法中,考察的是一條最短路徑上的"中間"結點,其中某條簡單路徑P=<V1,V2,...,Vj>的中間結點是P中除V1和Vj以外的任何結點,即任何屬於集合{V2,V3...,Vj-1}的結點。

  該算法主要基於下列觀察。設G的結點爲V={1,2,...,n},並對某個k考察其結點子集{1,2,...,k}。對任意一對結點i,j∈V,考察從i到j且其中間結點皆屬於集合{1,2,...,k}的所有路徑,設P是其中一條最小權路徑(因爲我們假定G中不包含負權迴路,所以P是簡單路徑)。Floyd-Warshall算法利用了路徑P與從i到j間的最短路徑(所有中間結點皆屬於集合{1,2,...,k-1}之間的聯繫。這一聯繫取決於k是否是路徑p的一箇中間結點。

  如果k是路徑p的中間結點,由如下圖所示,我們把p分解爲p1(i500){this.resized=true;this.style.width=500;}" align=absMiddle>k),p2(k500){this.resized=true;this.style.width=500;}" align=absMiddle>j)。由前面可知,p1是從i到k的一條最短路徑,且其所有中間結構均屬於集合{1,2,...,k}。
    500){this.resized=true;this.style.width=500;}" align=absMiddle>

  事實上,結點k不是路徑p1的中間結點,所以p1是從i到k的一條最短路徑,且滿足所有中間結點均屬於{1,2,...,k-1}。類似地,p2是從k到j的一條最短路徑,且其中間結點皆屬於集合{1,2,...,k-1}。

  (二)解決每對結點間最短路徑問題的一種遞歸方案

  基於上述觀察,我們將給出定義最短路徑估計的一個遞歸公式。設500){this.resized=true;this.style.width=500;}" align=absMiddle> 爲從結點i到結點j且滿足所有中間均屬於集合{1,2,...,k}的一條最短路徑的權。當k=0時,從結點i到結點j的路徑中根本不存在中間結點,因此它至多包含一條邊,則有500){this.resized=true;this.style.width=500;}" align=absMiddle> ,遞歸定義由下式給出:

    500){this.resized=true;this.style.width=500;}" align=absMiddle>

  矩陣給出了最後解,對所有的i,j∈V成立――因爲其所有中間點皆屬於{1,2,...,n}。500){this.resized=true;this.style.width=500;}" align=absMiddle>

  (三)自底向上計算最短路徑的權

  基於以上給出的遞歸定義,我們可以運用下面自底向上過程按k值的遞增順序計算500){this.resized=true;this.style.width=500;}" align=absMiddle> 。過程的輸入是n*n的矩陣W。其返回值關於最短路徑的權的矩陣。
  500){this.resized=true;this.style.width=500;}">

  (四) 建立最短路徑

  有時除了需要計算出一個帶權的有向圖中從任一頂點到其他頂點之間的最短路徑的長度外,還要確定相應的最短路徑。爲此,可以設置一個n*n的矩陣P,當k是在Floyd算法中,使Dij達到最小值時,就置P[i,j]=k。約定P[i,j]=0,表示從頂點i頂點j的最短路徑就是從i到j的邊。下面是修改後的Floyd算法,其中包含了計算矩陣矩陣P的步驟。

  500){this.resized=true;this.style.width=500;}" resized="true">
  Var
  Begin
   K:=P[i,j];
   If k=0 then return;
   Path(i,k);
   Print(k);
   Path(k,j);
  End;

  (五)源程序

  500){this.resized=true;this.style.width=500;}" resized="true">
  500){this.resized=true;this.style.width=500;}">
  500){this.resized=true;this.style.width=500;}">

  三、應用舉例

  1、設備更新問題。某企業使用一臺設備,在每年年初,企業領導部門就要決定是購置新的,還是繼續使用舊的。若購置新設備,就要支付一定的購置費用;若繼續使用舊設備,則需支付一定的維修費用。現在的問題是如何制定一個幾年之內的設備更新計劃,使得總的支付費用最少。
例如,我們一個五年之內要更新某種設備的計劃,若已知該種設備在各年年初的價格爲:

第一年
第二年
第三年
第四年
第五年
11
11
12
12
13

  還已知使用不同時間(年)的設備所需要的維修費用爲:

使用年數
0-1
1-2
2-3
3-4
4-5
維修費用
5
6
8
11
18

  可供選擇的設備更新方案顯然很多的,例如,每年都購置一臺新設備,則其購置費用爲11+11+12+12+13=59,而每年支付的維修費用爲5,五年合計爲25,於是五年總的支付費用爲59+25=84。

  雙如決定在第一、三、五年各購進一臺,這個方案的設備購置費爲11+12+13=36,維修費爲5+6+5+6+5=27。五年總的支付費用爲63。

  這個例子中一種最佳方案爲在第1年、第3年各購置一臺新設備,五年總費用爲53。
  編寫一個程序,輸入n年年初設備的價格與使用不同時間(年)的設備所需要的維修費用,爲該企業領導部門確定一個方案使得在n年內爲這臺機器支付的總費用最少。

  2、工程安排
  一項工程由多道工序組成, 按照施工過程的要求,這些工序之間,客觀上有一個必須遵守的先後關係。 對那些緊接在已知工序前的工序叫緊前工序,把在已知工序後邊緊接的工序叫後項工序, 只有已知工序的所有緊前工序都完成,已知工序才能開始施工。例如某工程的工序表如下:

序代號
緊前工序
完成時間
序代號
緊前工序
完成時間
A
-
6
F
C
2
B
-
2
G
D
3
C
A
3
H
B,E
4
D
A
5
I
H
2
E
A
3
J
F,G,I
2

  一天中可以同時進行若干道工序。

  編程要求:
    求工程最少在幾天內完成,並找出一種工程施工安排方案。

  輸入數據
    輸入文件名由鍵盤輸入,該文件
    第一行爲總工序數N;
    第二行至第N+1行爲工序表的內容(依次是工序編號1到N的工序代號、 緊前工序、工序完成時間。

  輸出數據
    輸出文件名爲OUTPUT.DAT,該文件有K+1行;
    第一行爲工程施工最短天數K
    第二行至第K+1行爲每一天施工的工序號

  參考文件
    參考輸入文件example.txt(上表)
    參考輸出文件answer.txt
    500){this.resized=true;this.style.width=500;}">

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章