最短路徑問題
問題抽象
在網絡中,求兩個不同頂點之間的所有路徑中,邊的權值之和最小的那一條路徑
- 這條路徑就是兩點之間的最短路徑(Shortest Path)
- 第一個頂點爲源點(Source)
- 最後一個頂點爲終點(Destination)
問題分類
- 單源最短路徑問題:從某固定源出發,求其到所有其他頂點的最短路徑
- (有向)無權圖
- (有向)有權圖
- 多源最短路徑問題:求任意兩頂點間的最短路徑
單源最短路算法
無權圖
按照路徑長度遞增(非遞減)的順序找出到各個頂點的最短路(遍歷方式跟BFS類似)
實現
- dist[W] : 源點
S 到頂點W 的最短距離
- dist[S]:源點
S 到源點S 的距離初始化爲0 - 其他的dist[W]可以被初始化爲正無窮、負無窮以及-1(算法中採用初始化爲-1)
- dist[S]:源點
- 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]爲源點s 到v 的最短路徑長度,但該路徑僅經過集合S 中的頂點。即路徑{s→(vi∈S)→v} 的最小長度 - 若路徑是按照遞增(非遞減)的順序生成的,則
- 真正的最短路必須只經過集合
S 中的頂點 - 每次從未收錄的頂點中選一個dist最小的收錄(貪心)
- 增加一個
v 進入集合S ,可能影響另外一個鄰接點w 的dist值
- 如果收錄
v 使得源點s 到w 的路徑變短,那麼s 到w 的路徑一定經過v ,且v 到w 有一條邊 dist[w]=min{dist[w],dist[v]+<v,w> 的權重}
- 如果收錄
- 真正的最短路必須只經過集合
實現
- collected[v]:記錄頂點
v 是否被收錄 - dist[W] : 源點
s 到頂點w 的最短距離
- dist[S]:源點
s 到源點s 的距離初始化爲0 - 其他的dist[W]這裏被初始化爲正無窮
- dist[S]:源點
- 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)
- 將dist存在最小堆中:
多源最短路算法
若有N個頂點、E條邊:
* 方法1:直接將單源最短路算法調用
* 方法2:Floyd算法,時間複雜度爲
Floyd算法
Dk[i][j]= 路徑{i→{l≤k}→j} 的最小長度D0,D1,...,DV−1[i][j] 即給出了i 到j 的真正最短距離D−1 被初始化爲帶權鄰接矩陣,對角元爲0,頂點之間沒有直接的邊相連則值初始化爲正無窮大- 當
Dk−1 已經完成,遞推到Dk 時:
- 如果
k∉ 最短路徑{i→{l≤k}→j} ,則Dk=Dk−1 - 如果
k∈ 最短路徑{i→{l≤k}→j} ,則該路徑必定由兩段最短路徑組成:Dk[i][j]=Dk−1[i][k]+Dk−1[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數組進行遞歸求解即可