如何計算圖的最短路徑?

算法導論(MIT 6.006 第15講 第16講 第17講)算法

最短路徑的定義是什麼?

最短路徑即擁有最小權重的路徑p;
路徑定義: p=<v_0,v_0,...,v_k>, 其中當0\leq i<k時,有 (v_i,v_{i+1}) \in E;
路徑的權重:w(p)=\Sigma{^{k-1}_{i=0}}w(v_i,v_{i+1}) ;bash

加上權重的數學表示方式

  1. 邊存在權重的圖:G(V,E,W) ,W是一個函數,做用於邊,生成一個實數,即W(E)->R
  2. 頂點到自身的路徑:(V_0)表示從(V_0)到(V_0)的路徑,權重是0
  3. 兩個頂點之間的最短路徑:
    \delta(u,v)=\lbrace{^{min\lbrace w(p)\rbrace{\quad}  u,v之間存在路徑}_{\infty{\qquad}u,v以前不存在路徑}}

E與V的關係 E=O(V^2 )。對於有向圖來說,假設有兩個頂點,v1,v2,他們之間只有4種鏈接狀況,依次類推網絡

爲何會有負的權重?

好比社交網絡上的喜歡能夠看作是正的權重,比喜歡能夠看作是負的權重函數

負權重的邊帶來什麼問題?

若是存在一個帶有負權重的邊,那麼每通過一個循環,會減小原有的權重值,這樣形成的現象是能夠獲得任何能夠獲得的權重值。好比路徑p=<S,A>權重是4,可是路徑p=<S,A,C,B,A>權重是3spa

最短路徑算法的通常思路是什麼?

d(v) 表示從源點s到當前節點v的路徑權重 ,\pi[v]表示當前最好的路徑上,v的前一個節點 ,經過這種方式就能重構整個最短路徑.net

針對沒有負權重的環3d

  1. 初始化 d[v] = \infty ,\pi[u]=NIL,d[s]=0
  2. 經過某種方式選擇邊(u,v),執行Relax操做,去更新源點到選擇的頂點的當前路徑值,以及選擇頂點的前一個節點
Relax(u,v,w):
    select edge(u,v):
        if d[v]>d[u]+w(u,v):
            d[v]=d[u]+w(u,v)
            PI[v]=u
    until all edges have d[v] <= d[u]+w(u,v)
複製代碼

relax操做的過程當中會不會產生一個一個比 \delta(s,v)還要小的值?

經過概括法,假設有 d[u] \geq \delta(s,u)。已知的是\delta(s,v)表示s到v的最短路徑,那麼任意一個到v的頂點u和源點s到u的最短路徑一定大於等於\delta(s,v),也就是code

\delta(s,v)\leq\delta(s,u)+w(u,v)

經過前面的假設,則一定有 \delta(s,v)\leq d[u]+w(u,v)=d[v] 。這說明,中間的過程的任意一個階段產生的結果d[v]都不會比\delta(s,v)還要小cdn

最短路徑算法的通常思路問題一:錯誤的選邊致使複雜度爲指數級別

構造以下結構的圖blog

邊的權值按照2^{n/2}方式分配,圖中給出的6個點的示例,若是所有顯示的邊(v_0,v_2)的權值爲2^{n/2},並依次遞減到1

假設源點爲 v_0,初始化選擇的路徑以下,能夠獲得從源點到各個點的路徑長度。

此時,Relax(v_4,v_6)的邊,會更新v_0v_6的路徑長度爲13

再Relax( v_2, v_4)的邊,會更新 v_0v_4的路徑長度爲10

因爲新 v_0v_4的路徑長度變短,那麼( v_0, v_5)的路徑會變短爲11

這個時候有可能先選的執行Relax的邊是 ( v_5, v_6),那麼( v_0, v_6)的路徑會變短爲12

再次Relax邊( v_4, v_6),那麼( v_0, v_6)的路徑會變短爲11

針對有n個頂點的狀況:

  • 首先Relax(v_{n-2},v_{n}),使得d[v_n]減1
  • 再Relax(v_{n-4},v_{n-2}),使得d[v_{n-2}]減2
  • 而後Relax(v_{n-2},v_{n-1})和(v_{n-1},v_{n})使得d[v_n]減1
  • 再執行Relax(v_{n-2},v_{n}),使得d[v_n]減1

可發現,當Relax的邊(v_{n-2},v_{n})權重爲1的時候,使得頂點d(v_n)減1;當Relax邊(v_{n-4},v_{n-2})權重爲2的時候,使得頂點d(v_n)減2,也就是從權重按照 1,2,4,...,2^{(n/2)-1},2^{(n/2)}的方式執行的過程當中,d(v_n)須要執行減小的總次數爲1+2+4+...+2^{(n/2)}=2^{(n/2)}-1,也就是說,會執行的次數爲指數級別

最短路徑算法的通常思路問題二:負權重環

若是在源點到目標節點通過的路徑上,通過環會致使權重減小,這個算法不會結束

如何獲取有向無環圖(DAG)中,單個源點到某個點的最短路徑?

DAG表示只是沒有環,能夠存在負邊權重

  1. 對DAG進行拓撲排序,這樣保證了u到v的路徑必定是u在v以前
  2. 找到源點,按照從左到右,DAG排列的順序,對通過的每一個頂點進行Relax操做,便獲得了源點到全部頂點的最短路徑

假設排序好的拓撲圖以下,對於初始化時,每一個源點到每一個節點的距離都認爲是 \infty

第一步從源點往下走,找到它的全部的邊,對邊執行Relax操做

源點執行完畢,而後按照拓撲排列的順序,從左往右執行,因爲Relax只更改小於的狀況,所以只有最後兩個節點的路徑值被更新

繼續往右執行Relax

繼續往右執行Relax

至此執行完畢,能夠看到源點到全部節點的最短路徑,從左到右分別是 \infty,0,2,6,5,3

若是圖中有環,可是通過這個環不會致使權重減小,如何計算最短路徑?

使用Dijkstra算法。僞代碼算法以下:

Dijkstra(G,w,s): //G是圖,w是權值,s是源點
    Initialize(G,s) // 初始化,設置d[s]=0,其它都是無窮,以及PI
    S <- {}    //已知最短路徑的點的集合
    Q <- V[G]  //須要被處理的頂點,能夠看作是一個最小優先級隊列,根據d()值進行排序
    while Q is not empty: //只要還有沒處理的節點
        u <- Extract-Min(Q) //從節點中找出一個最小的路徑權重的節點,並從Q中移除
        S <- S U {u} //將找到的節點併到S中
        for each vertex v belong to Adj
            Relax(u,v,w) //對邊的d()值進行更新
複製代碼

例子以下,選擇A爲源點

  1. 進行初始化,從A到其它節點的距離都是\infty,即 S={} ,Q={A(0),B(\infty),C(\infty),D(\infty),E(\infty)};
  2. 獲取隊列中的最小值,此時是A自己,此時S={A(0)},而後進行一次Relax操做,即發現A能達到的頂點爲B,C,更新後隊列中的值爲 Q={B(10),C(3),D(\infty),E(\infty)};
  3. 獲取隊列中的最小值,此時是C,S={A(0),C(3)},對選擇的C作Relax,C能到達的節點爲B,D,E,相應隊列更新爲:Q={B(7),D(11),E(5)};
  4. 獲取隊列的最小值,此時是E,S={A(0),C(3),E(5)},對選擇的E作Relax,E能到的節點爲D,因爲比現有的D值要大,因此沒有更新,Q={B(7),D(11)};
  5. 獲取隊列的最小值,此時是B,此時S={A(0),C(3),E(5),B(7)},B能到達的只剩下D了,B到D獲得的值爲9,要小,更新Q={D(9)}
  6. 獲取隊列最小的值,此時是D,此時S={{A(0),C(3),E(5),B(7),D(9)},至此結束。

括號中的值表示路徑距離

Dijkstra算法的時間複雜度

全部的耗時操做包括:

  • 將全部的頂點插入優先級隊列中,耗時爲 \theta(V);
  • 從優先級隊列中提取一個最小的值,耗時爲\theta(V);
  • Relax操做對邊進行d值減小,耗時爲\theta(E); 實現優先級隊列方式不一樣,耗時不一樣
  1. 使用Array。 提取最小值花銷:\theta(V),Relax對d值進行減小\theta(1),操做全部的隊列中的元素,那麼時間就是 \theta(V*V+E*1)=\theta(V^2)
  2. 使用最小堆。提取最小值花銷:\theta(\lg V),減小key的花銷\theta(\lg V),操做全部的隊列中的元素,那麼時間就是 \theta(V*\lg V+E*\lg V)
  3. 使用Fibonacci堆,提取最小值花銷:\theta(\lg V),減小key的花銷\theta(1),能到達\theta(V*\lg V+E)

最直觀的使用Dijkstra的感覺是:如下圖爲例:

假設綠色的點是源點,若是用這樣長度的繩子將各個節點鏈接起來,那麼拎起綠色的球,從上往下懸掛,那些蹦直的線相加就是源點到各個點的最短距離,好比綠色是源點,到其它點的最短距離分別是 7,12,18,22(顏色依次是紫色、藍色、黃色、紅色)

爲何Dijkstra不能處理負權重環的問題?

Dikstra不會去看已經處理好的節點,只會處理沒有看到的節點,若是已經處理的節點都是最小的值,再不存在負權重環的狀況下,是不會出現使得路徑變小的狀況。詳見:stackoverflow.com/questions/6…

若是在源點到目標節點通過的路徑上,有通過環且會致使權重減小,怎麼處理最短路徑問題?

使用Bellman-Ford算法。

Bellman-Ford(G,w,s):
    Initialuze(G,s)
    for i=1 to |V|-1:
        for each edge(u,v) belong to E:
            Relax(u,v,w)
    for each edge (u,v) belong to E:
        if d[v]>d[u]+w(u,v)
            report negative cycle exist
複製代碼

Bellman-Ford最終提供的是,若是沒有負權重的環,那麼能返回最短路徑(d[v]=\delta(s,v)),不然只是檢測出存在負權重的環

耗時分析

兩個for循環,分別爲V,E,因此時間複雜度就是O(VE)

爲何Bellman-Ford算法在不存在負權重環的狀況下可以計算最小路徑?

只須要證實,若是不存在負權重的環,那麼通過Bellman-Ford有d[v]=\delta(s,v)

取一條擁有最少邊的最短路徑p=<v_0,v_1,...,v_k>,其中v_0爲s,v_k=v。 若是不存在負權重的環,那麼說明p是一條簡單路徑,這代表,k\leq|V|-1。

這裏也不多是一個正環,即每通過這個環,權重增長,若是是那麼它就不是最短路徑了

當進行第一次循環的時候,取到的邊(v_0,v_1)進行了Relax,那麼有 \delta(s,v_1)=d[v_1] 進行第二次循環,取到的邊(v_1,v_2)進行了Relax,那麼有 \delta(s,v_2)=d[v_2]
那麼通過k輪循環以後,有\delta(s,v_k)=d[v_k],也就是說通過了|V|-1輪循環以後,每一個從源點可達的頂點都計算了最短路徑

簡單路徑(simple path):指除了起點和終點以外,其它頂點不會重複。對於簡單路徑p=<v_0,v_1,...,v_k>來說,若是k>=|V|,那麼路徑上總的頂點數是|V|+1,但實際只有 |V|個頂點,那麼一定存在一條重複的邊,使得非起點終點重複了,也就是說他不是簡單路徑了

爲何Bellman-Ford算法能檢測負權重環?

通過|V|-1輪循環以後,若是還有一條邊可以Relax,那麼當前從s到v的最短路徑並非簡單路徑,由於全部的節點都已經看過了,這時候確定存在了重複的節點,也就是說存在一個負權重的環

若是對一個路徑上有環,且全部權重值都是負權重,那麼使用Bellman-Ford算法能獲得最長路徑嗎?

不能,由於Bellman-Ford對於存在負權重的環的時候只會拋出異常,並無計算路徑,這實際是一個N-P的問題,即花的時間在指數級別或者之上

相似的,若是要求不通過負權重的環的狀況下,計算最短路徑,也並非件容易的事情

相關文章
相關標籤/搜索