-
單源最短路徑問題:從某固定源點出發,求其到所有其他頂點的最短路徑
- (有向)無權圖:BFS
- (有向)有權圖:Dijkstra算法
-
多源最短路徑問題:求任意兩頂點間的最短路徑
- 直接將單源最短路算法調用|V|遍
- Floyd算法
1.BFS算法求解單源無權圖最短路徑
1.1算法描述
廣度優先搜索,開一個額外的數組存儲每一個結點的訪問狀態,一層一層(取出隊首元素,遍歷所有相鄰且未被訪問的結點)的入隊列,然后層數++
這里的額外數組就是dist[w],指的是從源點到頂點w的最短路徑長度,初始化為-1,判斷未訪問即==-1,如果未訪問且存在邊G[v][w]
則dist[w] = dist[v] +1 ;
path數組用於保存每一個頂點w的前驅頂點v,也即這條最短路徑(s->w)必定是從(s->....->v->w),通過棧來逆序輸出path[w] 、path[path[w]]....
更加詳細的算法示例可以參考視頻
1.2代碼實現
#include<iostream>
#include<stdlib.h>
#include<cstdlib>
#include<queue>
#include<stack>
#define Init -1
#define MaxVertex 100
int path[MaxVertex]; // 存儲路徑,如果當前頂點v出隊列,且存在頂點v->w的路徑,則path[w] = v
int dist[MaxVertex]; // 存儲路徑長度,即從源頂點s到當前頂點w的最短路徑dist[w]
int G[MaxVertex][MaxVertex]; // 圖,采用鄰接矩陣表示
int Ne; // 頂點數
int Nv; // 邊
typedef int Vertex;
using namespace std;
void build(){
int v1,v2;
// 初始化點
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=1;i<=Nv;i++)
dist[i] = Init;
// 初始化邊
cin>>Ne;
for(int i=0;i<Ne;i++){
cin>>v1>>v2;
G[v1][v2] = 1; // 有向圖!
}
}
void Unweighted(Vertex v){
queue<Vertex> q;
dist[v] = 0; // 將自己的距離置 0 ,路徑path[v]不變
Vertex w;
q.push(v);
while(!q.empty()){
w = q.front();
q.pop();
for(int i=1;i<=Nv;i++)
// 如果沒被訪問過,且連通
if(dist[i]==Init && G[w][i]){
dist[i] = dist[w]+1; // 是上一步的距離 + 1
path[i] = w; // w 是上一步要走路徑的下一步路徑
q.push(i);
}
}
}
// 獲取路徑
void getTail(Vertex v){
for(int i=1;i<=Nv;i++){
if(i==v)
continue;
stack<Vertex> s;
cout<<v<<"到"<<i<<"的最短距離是:"<<dist[i];
Vertex w = i;
// 當沒到達起始起點前一直做循環
while(path[w]!=Init){
s.push(w); // 入棧
w = path[w];
}
// 逆序輸出入棧元素,得到路徑
cout<<" 其路徑為:";
if(v != i)
cout<<v;
while(!s.empty()){
// 輸出棧頂元素
cout<<"→"<<s.top();
s.pop(); // 出棧
}
cout<<endl;
}
}
int main(){
build();
Unweighted(3);
getTail(3);
return 0;
}
2.Dijkstra算法求解單源有權圖最短路徑
2.1算法描述
有權圖的單源最短路算法可以使用Dijkstra算法實現,Dijkstra算法的基本思想是對圖G(V,E)設置集合S,存放已被訪問的頂點,然后每次從集合V-S中選擇與起點s的最短距離最小的一個頂點(記為u),訪問並加入集合S。之后,令頂點u為中間點,優化所有起點s通過點u能夠到達的鄰接點v之間的最短路徑。這樣的操作執行n次,直到集合S已包含所有頂點。
算法的偽碼描述如下:
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[i]?
如何確定未收錄頂點中dist最小者?
1.直接掃描所有未收錄頂點,時間復雜度為– O( |V| ),總的時間復雜度為T = O( |V|^2 + |E| )對於稠密圖效果好
2.將dist存在最小堆中,時間復雜度為– O( log|V| ),總的時間復雜度為T = O( |V| log|V| + |E| log|V| ) = O( |E| log|V| ),對於稀疏圖效果好。
如何初始化dist[i]?
- 對於dist[0],也就是源點可以直接初始化為0
- 對於存在邊
G[0][w]
,則dist[w]可以直接初始化為頂點s到頂點w的邊權 - 其它的頂點w,初始化為infinity(無窮大)
- 初始狀態,兩個數組的初始化如上圖所示
對於算法的詳細示例可以參考視頻
2.2代碼實現
#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;
}
3.Floyd算法求解多源最短路徑算法
#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++)
cout<<dist[i][j]<<" ";
cout<<endl;
}
cout<<endl;
for(Vertex i=1;i<=Nv;i++){
for(Vertex j=1;j<=Nv;j++)
cout<<path[i][j]<<" ";
cout<<endl;
}
}
int main(){
build();
Floyd();
output();
return 0;
}
更多詳細的算法描述請參考視頻
以及文章