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