Dijksta+DFS求最短路徑問題

對於簡單的求最短路徑的問題,用Dijkstra算法就可以實現。當然,對於加了第二標尺的:比如:如果最短路徑有多條,選擇邊權最小的(花費最小)或者是點權最大的(物質數目多的),也可以用Dijkstra算法,在路徑相等的時候去更新第二標尺。然而,對於一些有多個標尺等更復雜的問題的時候。用Dijkstra+DFS算法。難度會降低許多。

Dijkstra+DFS思想

這個算法的思想是這樣的:首先通過Dijkstra算法計算出最短路徑,並且得到每個結點的前驅結點(也就是通過該前驅結點可以使得該點到起點的距離最小,這樣的結點可能是不唯一的)。這樣就可以得到所有的最短路徑,然後我們只需要在所有的最短路徑中計算出第2,第3等標尺。更新最優路徑即可。

Dijkstra實現

該算法和之前的類似,只不過之前用的是一個數組pre[n]來記錄每個頂點的前驅結點。這裏需要用vector<int>pre[n]來記錄每一個頂點的前驅結點(因爲結果可能不止一個)。同時,當存在頂點u,使得u到v的距離可以更優時,需要先清空pre[v]中的結點,然後將u加入。若存在相等的情況,則直接將u加入到v中。

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int INF = 0x3fffffff;
const int maxn = 520;
int G[maxn][maxn];//存儲圖
vector<int>pre[maxn]; //記錄每個頂點的前驅結點 
vector<int>path,tempPath; //記錄最終路徑和臨時路徑 
int optValue = INF; //第二標尺
int d[maxn];//最短路徑長度
bool vis[maxn]={false};
int n,m,st,ed; //頂點數,邊數,起點,終點 
//找最短路徑 
void Dijskstra(int s){
	fill(d,d+maxn,INF);
	d[s] = 0;
	for(int i=0; i<n; i++){
		int u = -1, MIN = INF;
		for(int j=0; j<n; j++){
			if(vis[j]==false && d[j] <MIN){
				u = j;
				MIN = d[j];
			}
		}
		if(u == -1) return;
		vis[u] = true;
		for(int v = 0; v <n ; v++){
			if(vis[v]==false && G[u][v] != INF){
				if(G[u][v] + d[u] < d[v]){
					d[v] = G[u][v]+d[u];
					pre[v].clear();
					pre[v].push_back(u);
				}
				if(G[u][v] + d[u] == d[v]){
					pre[v].push_back(u);
				}
			}
		}
	}
}

DFS實現

DFS是一個遞歸函數,考慮遞歸邊界和遞歸式。遞歸邊界就是到達起點。而遞歸式就是在到達起點前每次遞歸的去遍歷pre[v]中的點。這裏要注意最短路徑的求法。用tempPath來存放最短的路徑。在訪問v頂點時就可以把該頂點加入到tempPath中,然後遞歸遍歷pre[v]。要注意在到達起點的時候需要將起點也加入到tempPath中,這時候tempPath就存放了起點到終點的逆序。在此基礎上就可以進行其他標尺的計算了。然後需要壓出tempPath中最後加入的結點(起點),該層遞歸結束。

//這裏以花費爲第二標尺爲例
void DFS(int v){
	if(v==st){ //到達起點 
		tempPath.push_back(v); //壓入起點 
		int value = 0; //計算費用
		for(int i=tempPath.size()-1; i>0; i--){
			int index = tempPath[i];
			int next = tempPath[i-1];
			value += cost[index][next];
		}
		if(value < optValue){
			//更新
			optValue = value;
			path = tempPath; 
		}
		tempPath.pop_back();//彈出起點 
		return; 
	}
	tempPath.push_back(v); //壓入當前結點 
	for(int i=0; i<pre[v].size(); i++){
		DFS(pre[v][i]);
	}
	tempPath.pop_back(); //for循環結束,說明以末尾tempPath的結點去更新最短路徑的情況已經枚舉完,需要彈出 
}

通過上面的計算,最佳路徑被存儲在path中,最小花費(第二標尺)是optValue中。最短距離在d[n]中。這樣會很容易求出答案。

例題

最後記一到PAT A 1087 All Roads Lead To Rome 的題目。
這道題目第一標尺是花費(可以看作是結點之間的距離)。在花費相同的情況下,第二標尺是點權之和。若點權之和還是相同,這一平均點權最大來衡量最優。這道題用Dijkstra+DFS就容易做的多。因爲在最短路徑找到的情況下,求出最短路徑上的點權之和和平均點權十分容易。代碼如下:注意這裏需要將城市名和下標對應。自己在解題的時候,在城市名和點權輸入的時候只映射了城市名到下標,忘了映射下標到城市名。。。結果不知道爲何例子跑得出來就是一直是0分。。。

#include<iostream>
#include<string>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
const int INF = 0x3fffffff;
const int maxn = 250;
int G[maxn][maxn];
int d[maxn],weight[maxn];//最短路徑(花費),結點權重 
bool vis[maxn]={false}; 
vector<int>pre[maxn];//記錄前驅結點
vector<int>tempPath,path;
int optSumHappiness=0;
double optAvgHappiness=0;
int n,m,st; //結點數,路徑條數,起點 
map<string,int>cityToIndex;
map<int,string>indexToCity;
int nums = 0; //最短路徑的條數 

void Dijsktra(int s){
	fill(d,d+maxn,INF);
	d[s] = 0;
	for(int i=0; i<n; i++){
		int u = -1,MIN = INF;
		for(int j=0; j<n; j++){
			if(vis[j]==false && d[j]<MIN){
				u = j;
				MIN = d[j];
			}
		}
		if(u == -1) return;
		vis[u] = true;
		for(int v=0; v<n; v++){
			if(vis[v] == false && G[u][v] != INF){
				if(d[u] + G[u][v] < d[v]){
					d[v] = d[u]+G[u][v];
					pre[v].clear();
					pre[v].push_back(u);
				}else if(d[u]+G[u][v]==d[v]){
					pre[v].push_back(u);
				}
			}
		}
	} 
}


void DFS(int v){
	//到達邊界 
	if(v == st){
		nums++; 
		tempPath.push_back(v); //加入起點
		int sumHappiness = 0;
		for(int i=tempPath.size()-2; i>=0; i--){
			int index = tempPath[i];
			sumHappiness += weight[index];
		}
		double avgHappiness = 1.0*sumHappiness / (tempPath.size()-1);
		if(sumHappiness > optSumHappiness){
			optSumHappiness = sumHappiness;
			optAvgHappiness = avgHappiness;
			path = tempPath;
		}else if(sumHappiness == optSumHappiness){
			if(avgHappiness > optAvgHappiness){
				optAvgHappiness = avgHappiness;
				path = tempPath;
			}
		}
		tempPath.pop_back();
		return;
	}
	tempPath.push_back(v);
	for(int i=0; i<pre[v].size();i++){
		DFS(pre[v][i]);	
	}
	tempPath.pop_back();
}

int main(){
	string start,city1,city2;
	cin>>n>>m>>start;
	cityToIndex[start] = 0;
	indexToCity[0] = start;
	for(int i=1;i<=n-1; i++){
		cin>>city1>>weight[i];
		cityToIndex[city1] = i;
		indexToCity[i] = city1;
	}
	
	//初始化爲無窮
	fill(G[0],G[0]+maxn*maxn,INF); 
	for(int i=0; i<m; i++){
		cin>>city1>>city2;
		int c1 = cityToIndex[city1],c2 = cityToIndex[city2];
		cin>>G[c1][c2];
		G[c2][c1] = G[c1][c2];
	}
	Dijsktra(0);
	int rom = cityToIndex["ROM"];
	DFS(rom);
	printf("%d %d %d %d\n",nums,d[rom],optSumHappiness,(int)optAvgHappiness);
	for(int i = path.size()-1; i>=0; i--){
		cout<<indexToCity[path[i]];
		if(i != 0) cout<<"->";
	}
	return 0;
	
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章