資料結構(十四)最短路問題
阿新 • • 發佈:2018-11-25
最短路徑問題
1. 概述
1. 抽象
在網路(帶權圖)中,求兩個不同頂點之間的所有路徑中,邊的權值之和最小的那一條路徑
- 這條路徑就是兩點之間的最短路徑(ShorttestPath)
- 第一個頂點為源點(Source)
- 最後一個頂點為終點(Destination)
2. 分類
- 單源最短路徑問題:從某固定源點出發,求其到所有其他頂點的最短路徑
- (有向)無權圖
- (有向)有權圖
- 多源最短路徑問題:求任意兩頂點間的最短路徑
2. 無權圖的單源最短路演算法
按照遞增(非遞減)的順序找出到各個頂點的最短路
void Unweighted( Vertex s){
queue<Vertex> q;
q.push(s);
wile(!q.empty()){
v = q.front(); q.pop();
for( V 的每個臨界點 W){
dist[W] = dist[v] + 1; // 當前距離上一距離 + 1
path[W] = v; // s 到 w 的必經頂點就是前一個頂點 v
q.push(W);
}
}
}
3. 有權圖的單源最短路演算法
Dijkstra 演算法
- 令 S = {源點s + 已經確定了最短路徑的頂點 v }
- 對任一未收錄的頂點 v,定義 dist[v] 為 s 到 v 的最短路徑長度,但該路徑僅經過 S 中的頂點。即路徑 {s→(v ∈S)→v} 的最小長度
- 若路徑是按照遞增(非遞減)的順序生成的,則
- 真正的最短路必須只經過 S 中的頂點
- 每次從未收錄的頂點中選一個 dist 最小的收錄
- 增加一個 v 進入 S,可能影響另外一個 w 的 dist 值
- dist[w] = min{dist[w],dist[v] + <v,w>的權重}
void Dijkstra( Vertex s ){
while(1){
V = 未收錄頂點中dist最小值;
if( 這樣的V不存在 )
break;
collected[V] = true;
for( V 的每個鄰接點 W )
if( collected[W] == false )
if(dist[V] + E<V,W> < dist[W]){
dist[W] = dist[V] + E<V,W>;
path[W] = V;
}
}
}
取出未收錄頂點中dist最小值 和 更新dist[W]的操作可以考慮兩種方法:
-
直接掃描所有未收錄頂點 ——O(|V|)
T = O(|V| + |E|) ——稠密圖效果更好
-
將dist存在最小堆中 ——O(log|V|)
更新dist[w]的值 —O(log|V|)
T = O(|E|log|V|) —— 稀疏圖效果更好
#include<iostream>
#include<stdlib.h>
#define Inf 1000000
#define Init -1
#define MaxVertex 100
typedef int Vertex;
int G[MaxVertex][MaxVertex];
int dist[MaxVertex]; // 距離
int path[MaxVertex]; // 路徑
int collected[MaxVertex]; // 被收錄集合
int Nv; // 頂點
int Ne; // 邊
using namespace std;
// 初始化圖資訊
void build(){
Vertex v1,v2;
int w;
cin>>Nv;
// 初始化圖
for(int i=1;i<=Nv;i++)
for(int j=1;j<=Nv;j++)
G[i][j] = 0;
// 初始化路徑
for(int i=1;i<=Nv;i++)
path[i] = Init;
// 初始化距離
for(int i=0;i<=Nv;i++)
dist[i] = Inf;
// 初始化收錄情況
for(int i=1;i<=Nv;i++)
collected[i] = false;
cin>>Ne;
// 初始化點
for(int i=0;i<Ne;i++){
cin>>v1>>v2>>w;
G[v1][v2] = w; // 有向圖
}
}
// 初始化距離和路徑資訊
void crate(Vertex s){
dist[s] = 0;
collected[s] = true;
for(int i=1;i<=Nv;i++)
if(G[s][i]){
dist[i] = G[s][i];
path[i] = s;
}
}
// 查詢未收錄頂點中dist最小者
Vertex FindMin(Vertex s){
int min = 0; // 之前特地把 dist[0] 初始化為正無窮
for(Vertex i=1;i<=Nv;i++)
if(i != s && dist[i] < dist[min] && !collected[i])
min = i;
return min;
}
void Dijkstra(Vertex s){
crate(s);
while(true){
Vertex V = FindMin(s); // 找到
if(!V)
break;
collected[V] = true; //收錄
for(Vertex W=1;W<=Nv;W++)
if(!collected[W] && G[V][W]){ // 如果未被收錄
if(dist[V] + G[V][W] < dist[W]){
dist[W] = G[V][W] + dist[V];
path[W] = V;
}
}
}
}
void output(){
for(int i=1;i<=Nv;i++)
cout<<dist[i]<<" ";
cout<<endl;
for(int i=1;i<=Nv;i++)
cout<<path[i]<<" ";
cout<<endl;
}
int main(){
build();
Dijkstra(1);
output();
return 0;
}
4. 多源最短路演算法
-
直接將單源最短路演算法呼叫|V|遍
T = O(|V| + |E|×|V|) ——對於稀疏圖效果好
-
Floyd 演算法
T = O(|V| ) ——對於稠密圖效果好
Floyd 演算法
- D = 路徑{ i →{ ≤ } → j } 的最小長度
- D ,D ,…,D 即給出了 i 到 j 的真正最短距離
- 最初的 D 是全 0 的鄰接矩陣
- 若 i 和 j 不直接相連,初始化為無窮大
- 當 D
已經完成,遞推到 D
時:
- 或者 不屬於 最短路徑 { i →{ ≤ } → j },則 D = D
- 或者 屬於最短路徑 { i →{ ≤ } → j },則該路徑必定由兩段最短路徑組成:D = D + D
void Floyd(){
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;
}
}
#include<iostream>
#include<stdlib.h>
#define INF 1000000
#define MaxVertex 100
typedef int Vertex;
int G[MaxVertex][MaxVertex];
int dist[MaxVertex][MaxVertex]; // 距離
int path[MaxVertex][MaxVertex]; // 路徑
int Nv; // 頂點
int Ne; // 邊
using namespace std;
// 初始化圖資訊
void build(){
Vertex v1,v2;
int w;
cin>>Nv;
// 初始化圖
for(int i=1;i<=Nv;i++)
for(int j=1;j<=Nv;j++)
G[i][j] = INF;
cin>>Ne;
// 初始化點
for(int i=0;i<Ne;i++){
cin>>v1>>v2>>w;
G[v1][v2] = w;
G[v2][v1] = w;
}
}
void Floyd(){
for(Vertex i=1;i<=Nv;i++)
for(Vertex j=1;j<=Nv;j++){
dist[i][j] = G[i][j];
path[i][j] = -1;
}
for(Vertex k=1;k<=Nv;k++)
for(Vertex i=1;i<=Nv;i++)
for(Vertex j=1;j<=Nv;j++)
if(dist[i][k] + dist[k][j] < dist[i][j]){
dist[i][j] = dist[i][k] + dist[k][j];
path[i][j] = k;
}
}
void output(){
for(Vertex i=1;i<=Nv;i++){
for(Vertex j=1;j<=Nv;j