【algo&ds】7.最短路徑問題


  • 單源最短路徑問題:從某固定源點出發,求其到所有其他頂點的最短路徑

    • (有向)無權圖:BFS
    • (有向)有權圖:Dijkstra算法
  • 多源最短路徑問題:求任意兩頂點間的最短路徑

    • 直接將單源最短路算法調用|V|遍
    • Floyd算法

1.BFS算法求解單源無權圖最短路徑

1.1算法描述

Snipaste_2019-11-23_09-53-57.png

廣度優先搜索,開一個額外的數組存儲每一個結點的訪問狀態,一層一層(取出隊首元素,遍歷所有相鄰且未被訪問的結點)的入隊列,然后層數++

Snipaste_2019-11-23_10-02-44.png

Snipaste_2019-11-23_10-05-46.png

這里的額外數組就是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算法求解單源有權圖最短路徑

Dijkstra算法圖解.png

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(無窮大)

Snipaste_2019-11-23_15-56-54.png

  • 初始狀態,兩個數組的初始化如上圖所示

對於算法的詳細示例可以參考視頻

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;
}

更多詳細的算法描述請參考視頻

以及文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM