1. 程式人生 > >迪傑斯特拉(Dijkstra)演算法描述及其正確性證明

迪傑斯特拉(Dijkstra)演算法描述及其正確性證明

1. 演算法描述

Dijkstra演算法是圖論中常用的一個演算法,用於計算圖中從一個指定點到其餘所有點的最短路徑。圖是有向圖,所有邊的權重為非負數,圖1是滿足條件的一個簡單有向圖。

圖1 有向加權圖示例

在圖1中,A到D的最短路徑是A-->C-->B-->D,其長度為3+2+5=10。

其演算法實現程式碼如下(資料結構與演算法分析C++版):

void Dijkstra(Graph *G, int *D, int s){
  int i, v, w;

  for(i = 0; i < G->n(); i++){
    D[i] = INFINITY;
  }
  D[0] = 0;
  for(i = 0; i < G->n(); i++){
    v = minVertex(G, D);
    if(D[v] == INFINITY)
      return;
    G->setMark(v, VISITED);
    for(w = G->first(v); w < G->n(); w = G->next(v, w))
      if(D[w] > (D[v] + G->weight(v,w)))
        D[w] = D[v] + G->weight(v, w);
  }
}

int minVertex(Graph *G, int *D){
  int i, v=-1;

  for(i = 0; i < G->n(); i++){
    if(G->getMark(i) == UNVISITED){
      v = i;
      break;
    }
  }
  for(i++; i < G->n(); i++){
    if((G->getMark(i) == UNVISITED) && (D[i] < D[v]))
    v = i;
  }
  return v;
}

演算法中的每個結點使用整數表示,陣列D[ ]定義了從出發點s到每個點v的最短路徑,被初始化為INFINITY。另外,每個結點還有一個標識(Mark),通過函式setMark和getMark來設定和讀取。標識具有兩種狀態:VISITED和UNVISITED。演算法的工作步驟被描述如下:

1. 把所有結點的標識都設為UNVISITED,距離設為INFINIT。

2. 把出發點s的距離設定為0,狀態設定為VISITED。

3. 從圖中所有UNVISITED的點中選擇距離最小的結點v。

4. 設定v的狀態為VISITED。

5. 用v來更新其相鄰結點。若某相鄰結點u的當前距離D[u] > D[v] + weight(v, u),則把u的距離D[u]更新為D[v] + weight(v, u)。

6. 重複3到5步的操作,直到所有結點狀態都被設定為VISITED。

以圖1為例,演算法的計算過程為:

1. D[A] = 0。

2. 選擇D[A]為距離最小的結點,設其狀態為VISITED;更新D[B] = 10, D[C] = 3, D[D] = 20。

3. 選擇D[C]為距離最小的結點,設其狀態為VISITED;更新D[B] = D[C] + weight(C, B) = 3 + 2 = 5, D[E] = D[C] + weight(C, E) = 3 + 15 =18。

4. 選擇D[B]為距離最小的結點,設其狀態為VISITED;更新D[D] = D[B] + weight(B, D) = 5 + 5 = 10。

5. 選擇D[D]為距離最小的結點,設其狀態為VISITED;與D相連的結點E的距離D[E] = 18,小於D[D] + weight(D, E) = 10 + 11 = 21,所以不需要更新。

6. 選擇D[D]為距離最小的結點,設其狀態為VISITED;沒有相連的點,所以不需要更新。

所以最終每個點的最小距離為D[A] = 0; D[B] = 5]; D[C] = 3]; D[D] = 10; D[E] = 18.

2. 演算法正確性證明

上一節描述的Dijkstra演算法把圖中的結點分為兩個部分,分別標記為VISITED和UNVISITED,使用S和V-S來表示。為證明的方便,區分了S和V-S中的點的距離函式,分別為D和D_est,V-S中的距離函式被稱為估值函式。演算法的主要操作是迴圈執行第3到5步。證明演算法的正確性,可以通過證明每次迴圈執行之前,S和V-S中的結點滿足以下3條屬性,執行之後依然滿足。

屬性1. S中任意m的點的的路徑長度D[m]就是其最短路徑。

屬性2. 估值函式滿足下述條件:

也就是說,對於V-S中的任意結點n,其估值路徑D_est[n]是其只通過S中結點的最短路徑。

屬性3. V-S中估值最小的點n,D_est[n]的值就是其最短路徑。

演算法第一步S={s},只包含出發結點,且D[s]=0,故而滿足屬性1,更新相鄰結點之後,容易證明,也滿足屬性2。屬性3則需要證明,過程如下。

證明:假設D_est[n]不是n的最短路徑,因為D_est是隻通過S中結點的最短路徑,所以結點n的真實的最短路徑必然會經過集合S之外的結點,設路徑上第一個非S中的點為j。則真實的最短路徑的形式為s->...->j->...->n。因為假設了j之前的點都是S中的,所以根據屬性2,D_est[j] < =D[n] < D_est[n],與n是估值最小的結點矛盾,所以屬性成立。

演算法接下來的操作是把n加入S,並試圖更新V-S中結點的距離估值,容易證明,3-5步的一次操作之後,屬性1-3仍然滿足,所以得證。