13. 圖--最短路徑問題

最短路徑問題

問題抽象

在網絡中,求兩個不同頂點之間的所有路徑中,邊的權值之和最小的那一條路徑

  • 這條路徑就是兩點之間的最短路徑(Shortest Path)
  • 第一個頂點爲源點(Source)
  • 最後一個頂點爲終點(Destination)

問題分類

  • 單源最短路徑問題:從某固定源出發,求其到所有其他頂點的最短路徑
    • (有向)無權圖
    • (有向)有權圖
  • 多源最短路徑問題:求任意兩頂點間的最短路徑

單源最短路算法

無權圖

按照路徑長度遞增(非遞減)的順序找出到各個頂點的最短路(遍歷方式跟BFS類似)

實現

  • dist[W] : 源點S 到頂點W 的最短距離
    • dist[S]:源點S 到源點S 的距離初始化爲0
    • 其他的dist[W]可以被初始化爲正無窮、負無窮以及-1(算法中採用初始化爲-1)
  • path[W]:源點S 到頂點W 的路上經過的某頂點
    • 獲取源點S 到頂點W 的路徑:從W 開始去反向遍歷到S 即可獲取到反向的路徑,再由棧進行正向的處理即可
void Unweighted(Vertex S) {
    Enqueue(S, Q);

    while (!IsEmpty(Q)) {
        V = Dequeue(Q);

        for (V 的每個鄰接點 W) {
            if (dist[W] == -1) { // 說明還沒有訪問過
                dist[W] = dist[V] + 1;  // 爲上一個頂點的距離+1
                path[W] = V;        // 記錄爲上一個頂點
                Enqueue(W, Q);
            }
        }
    }
}

若有N個頂點、E條邊,時間複雜度是

  • 用鄰接表存儲圖,爲O(N+E)
  • 用鄰接矩陣存儲圖,爲O(N2)

有權圖

按照路徑長度遞增(非遞減)的順序找出到各個頂點的最短

Dijkstra 算法用於解決有權圖的單源最短路問題。但是不能解決有負邊的情況

Dijkstra 算法

  • S={ 源點s +  已經確定了最短路徑的頂點vi}
  • 對任一爲收錄的頂點v ,定義dist[v]爲源點sv 的最短路徑長度,但該路徑僅經過集合S 中的頂點。即路徑{s(viS)v} 的最小長度
  • 若路徑是按照遞增(非遞減)的順序生成的,則
    • 真正的最短路必須只經過集合S 中的頂點
    • 每次從未收錄的頂點中選一個dist最小的收錄(貪心)
    • 增加一個v 進入集合S ,可能影響另外一個鄰接點w 的dist值
      • 如果收錄v 使得源點sw 的路徑變短,那麼sw 的路徑一定經過v ,且vw 有一條邊
      • dist[w]=min{dist[w],dist[v]+<v,w> 的權重}

實現

  • collected[v]:記錄頂點v 是否被收錄
  • dist[W] : 源點s 到頂點w 的最短距離
    • dist[S]:源點s 到源點s 的距離初始化爲0
    • 其他的dist[W]這裏被初始化爲正無窮
  • path[W]:源點s 到頂點w 的路上經過的某頂點
    • 獲取源點s 到頂點w 的路徑:從w 開始去反向遍歷到s 即可獲取到反向的路徑,再由棧進行正向的處理即可
void Dijkstra(Vertex s) {
    while (true) {
        V = 未收錄頂點中dist最小者;
        if ( 這樣的V不存在 )
            break;

        collected[V] = true;
        for ( V 的每個鄰接點 W) {
            if(!collected[W] && dist[V] + E<V, W> < dist[W]) {
                dist[W] = dist[V] + E<V, W>;
                path[W] = V;
            }
        }
    }
}

若有N個頂點、E條邊,根據尋找未收錄頂點中dist最小者的方式來決定,時間複雜度是

  • 方法1,更適用與稠密圖
    • 直接掃描所有未收錄頂點:O(N)
    • 最終的時間複雜度:T=O(N2+E)
  • 方法2,更適用於稀疏圖
    • 將dist存在最小堆中:O(logN)
    • 更新dist[w]的值:O(logN)
    • 最終的時間複雜度:T=O(NlogN+ElogN)=O(ElogN)

多源最短路算法

若有N個頂點、E條邊:
* 方法1:直接將單源最短路算法調用N 遍,時間複雜度爲T=O(N3+NE) ,適合稀疏圖
* 方法2:Floyd算法,時間複雜度爲O(N3) ,適合稠密圖

Floyd算法

  • Dk[i][j]= 路徑{i{lk}j} 的最小長度
  • D0,D1,...,DV1[i][j] 即給出了ij 的真正最短距離
  • D1 被初始化爲帶權鄰接矩陣,對角元爲0,頂點之間沒有直接的邊相連則值初始化爲正無窮大
  • Dk1 已經完成,遞推到Dk 時:
    • 如果k 最短路徑{i{lk}j} ,則Dk=Dk1
    • 如果k 最短路徑{i{lk}j} ,則該路徑必定由兩段最短路徑組成Dk[i][j]=Dk1[i][k]+Dk1[k][j]

實現

void Floyd() {
    int i, j, k;
    for (i = 0; i < N; i++)
        for (j = 0; j < N; j++) {
            D[i][j] = G[i][j];
            path[i][j] = -1;
        }

    for (k = 0; k < N; k++) 
        for (i = 0; i < N; i++)
            for (j = 0; j < N; j++) 
                if (D[i][k] + D[k][j] < D[i][j]) {
                    D[i][j] = D[i][k] + D[k][j];
                    path[i][j] = k;
                }

}

如果需要求出最短路徑經過哪些點,通過path數組進行遞歸求解即可

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