Dijkstra詳解

簡述:

讓我們考慮一下沒有負邊的情況。在Bellman-Ford算法中,如果dp[i]還不是最短距離的話,那麼即使進行dp[j]=d[i]+(從I 到j的邊的權值)的更新,dp[j]也不會變成最短距離。而且,即使dp[i]沒有變化,每一次循環也要檢查-遍從出發的所有邊。這顯然是很浪費時間的。因此可以對算法做如下修改。

(1)找到最短距離已經確定的頂點,從它出發更新相鄰頂點的最短距離。

(2)此後不需要再關心1中的“最短距離已經確定的頂點”。

定義:

在(1)和(2)中提到的“最短距離已經確定的頂點”要怎麼得到是問題的關鍵。在最開始時,只有起點的最短距離是確定的。而在尚未使用過的頂點中,距離dp[i]最 小的頂點就是最短距離已經確定的頂點。這是因爲由於不存在負邊,所以dp[i]不會在之後的更新中變小。這個算法叫做Dijkstra算法。

 

優化一:

使用鄰接矩陣實現的Djkstra算法的複雜度是O(V^2)。使用鄰接表的話,更新最短距離只需要訪問

每條邊一次即可,因此這部分的複雜度是O(E)。但是每次要枚舉所有的頂點來查找下一個使用

的頂點,因此最終複雜度還是O(v^2)。在E比較小時,大部分的時間花在了查找下一個使用的頂

點上,因此需要使用合適的數據結構對其進行優化。首先想到了集合,使用vector來模擬集合即可。

實現:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int n,m;//n個頂點m條邊
const int INF=1e9;
vector<vector<int>>mp(1003,vector<int>(1003,INF));
vector<int>dp(1003,INF);
vector<int>unused;
void Dijkstra(int s) {
	dp[s]=0;
	unused.resize(n);
	for(int i=0; i<n; ++i)
		unused[i]=i;
	while(1) {
		int v=-1;
		for(int i=0; i<unused.size(); ++i)
			if((v==-1||dp[unused[i]]<dp[v]))//在沒使用的頂點集中尋找離生成集合最近的點
				v=unused[i];
		if(v==-1)//若已沒有沒使用的點了,證明最短路徑已全部更新完畢
			break;
		unused.erase(find(unused.begin(),unused.end(),v));//加入找到的點到生成集合中
		for(int i=0; i<unused.size(); ++i)
			dp[unused[i]]=min(dp[unused[i]],dp[v]+mp[v][unused[i]]);//更新因該點而改變未使用的點的最小dp值
	}
}
int main() {
	cin>>n>>m;
	for(int i=0; i<m; ++i) {
		int a,b,c;
		cin>>a>>b>>c;
		mp[a][b]=c;
		mp[b][a]=c;
	}
	int s;
	cin>>s;
	Dijkstra(s);
	for(int i=0; i<n; ++i)
		cout<<dp[i]<<" ";
}

 

優化二:

需要優化的是數值的插入(更新)和取出最小值兩個操作,因此使用堆就可以了。把每個頂點當

前的最短距離用堆維護,在更新最短距離時,把對應的元素往根的方向移動以滿足堆的性質。而

每次從堆中取出的最小值就是下一次要使用的頂點。這樣堆中元素共有O(V)個,更新和取出數

值的操作有0(E)次,因此整個算法的複雜度是O(E *logV )。

下面是使用STL的priority_ queue 的實現。在每次更新時往堆裏插人當前最短距離和頂點的值對。插入的次數是O(E)次,因此元素也是O(E)個。當取出的最小值不是最短距離的話,就丟棄這個值。這樣整個算法也可以在同樣的複雜度內完成。

來一個Pro版本: 

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
int n,m;//n個頂點m條邊
const int INF=1e9;
typedef pair<int,int> p;//first爲dp值,second爲頂點號 
vector<vector<int>>mp(1003,vector<int>(1003,INF));
vector<int>dp(1003,INF);
vector<int>used(1003,0);
void Dijkstra_Pro(int s) {
	dp[s]=0;
	priority_queue<p,vector<p>,greater<p>> q;
	q.push(p(0,s));
	while(!q.empty()) {
		p temp=q.top();
		q.pop();
		int v=temp.second;
		if(dp[v]<temp.first)//如果其最新dp值與過去的某一個更新時保存的dp值大小不一時放棄選擇,因爲過去的dp值已無效。
			continue;
		used[v]=1;
		for(int i=0; i<n; ++i)
			if(!used[i]) {
				int t=dp[i];
				dp[i]=min(dp[i],dp[v]+mp[v][i]);//更新因該點而改變未使用的點的最小dp值 
				if(t!=dp[i])//當且僅當dp[i]已更新時才加入優先隊列中 
					q.push(p(dp[i],i));
			}
	}
}
int main() {
	cin>>n>>m;
	for(int i=0; i<m; ++i) {
		int a,b,c;
		cin>>a>>b>>c;
		mp[a][b]=c;
		mp[b][a]=c;
	}
	int s;
	cin>>s;
	Dijkstra_Pro(s);
	for(int i=0; i<n; ++i)
		cout<<dp[i]<<" ";
}

 

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