離散數學課本上的最短路徑演算法
演算法描述:
節點集合V = {}空集合,距離初始化。
節點編號0..n – 1, 起點編號0≤ s < n。
距離陣列
起點 d[s] = 0
其他 d[i] = ∞, 0 ≤ i < n, i ≠ s。
迴圈n次
找到節點i 不屬於 V,且d[i]值最小的節點i。
V = V + i
對所有滿足j V的邊(i, j) 更新d[j] = min(d[j] , d[i] + w(i, j))。
以下圖為例,描述Dijkstra演算法的執行過程:
初始,求A點到其他點的最短路徑(也稱單源最短路徑)。
初始化A點
A點有3條邊,AB(17),AE(16),AF(1)。
將3條邊加入優先佇列,此時佇列中的元素為(只記錄目標點):
{1 F} | {16 E} | {17 B} 取出佇列中最小的元素,{1 F},F點是一個未處理過的點,因此得到了A點到F點的最短距離。更新距離,變為:
處理F點,F點有4條邊。FA(1),FB(11),FD(14),FE(33)。其中FA已經處理過,所以忽略掉。
將3條邊加入優先佇列,注意,此時加入佇列時,所有邊的權值需要加上F點到A點的最短距離1。此時佇列中的元素為:
{12 B} | {15 D} | {16 E} | {17 B} | {34 E} 取出佇列中最小的元素,{12 B},B點是一個未處理過的點,因此得到了A點到B點的最短距離。更新距離,變為:
將2條的權值加上A到B的最短路徑12,加入優先佇列。此時佇列中的元素為:
{15 D} | {16 E} | {17 B} | {17 D} | {18 C} | {34 E}
取出佇列中最小的元素,{15 D},D點是一個未處理過的點,因此得到了A點到D點的最短距離。更新距離,變為:
處理D點,D點有4條邊。其中DC(10),DE(4)沒有處理過。
將2條的權值加上A到D的最短路徑15,加入優先佇列。此時佇列中的元素為:
{16 E} | {17 B} | {17 D} | {18 C} | {19 E} | {25 C} | {34 E}
取出佇列中最小的元素,{16 E},E點是一個未處理過的點,因此得到了A點到E點的最短距離。更新距離,變為: 處理E點,E點所連線的邊都已經被處理過了。
此時優先佇列中的元素為:
{17 B} | {17 D} | {18 C} | {19 E} | {25 C} | {34 E}
取出佇列中最小的元素,{17 B},B點是一個已經處理過的點,因此繼續後面的處理。 {17 D} | {18 C} | {19 E} | {25 C} | {34 E}
取出佇列中最小的元素,{17 D},D點是一個已經處理過的點,因此繼續後面的處理。
{18 C} | {19 E} | {25 C} | {34 E}
取出佇列中最小的元素,{18 C},C點是一個未處理過的點,因此得到了A點到C點的最短距離。更新距離,變為:
Dijkstra演算法的證明:
i V, d[i] = min{d[x] + w(x, i), x V}
我們證明節點i要進入集合V時,d[i]確實是s到i的最短路長度 。
歸納證明: 起初 d[s] = 0滿足條件。 假設之前集合V中的點全部滿足假設,現在要加入節點i V,假設任意從s到i的路徑P= s…x y…i。
其中s..x全部在V中, y V。根據歸納假設d[x]是s到x的最短路長度。 根據d的定義,我們有d[x] + w(x,y) ≥ d[y]。
而且因為dijkstra選擇最小的d加入,所以有d[y] ≥ d[i] 。
於是有路徑P的長度, length(P) ≥ d[x] + w(x, y) + length(y..i) ≥ d[y] + length(y..i) ≥ d[y] ≥ d[i]。
從而d[i]也是最短路的長度。得證。
例題:
你來到一個迷宮前。該迷宮由若干個房間組成,每個房間都有一個得分,第一次進入這個房間,你就可以得到這個分數。還有若干雙向道路連結這些房間,你沿著這些道路從一個房間走到另外一個房間需要一些時間。遊戲規定了你的起點和終點房間,你首要目標是從起點儘快到達終點,在滿足首要目標的前提下,使得你的得分總和儘可能大。現在問題來了,給定房間、道路、分數、起點和終點等全部資訊,你能計算在儘快離開迷宮的前提下,你的最大得分是多少麼?
最後,我們來提供輸入輸出資料,由你來寫一段程式,實現這個演算法,只有寫出了正確的程式,才能繼續後面的課程。
輸入第一行4個整數n (<=500), m, start, end。n表示房間的個數,房間編號從0到(n - 1),m表示道路數,任意兩個房間之間最多隻有一條道路,start和end表示起點和終點房間的編號。 第二行包含n個空格分隔的正整數(不超過600),表示進入每個房間你的得分。 再接下來m行,每行3個空格分隔的整數x, y, z (0<z<=200)表示道路,表示從房間x到房間y(雙向)的道路,注意,最多隻有一條道路連結兩個房間, 你需要的時間為z。 輸入保證從start到end至少有一條路徑。
輸出
一行,兩個空格分隔的整數,第一個表示你最少需要的時間,第二個表示你在最少時間前提下可以獲得的最大得分。輸入示例
3 2 0 2 1 2 3 0 1 10 1 2 11
輸出示例
21 6
程式碼:
#include<iostream>
#include<cmath>
using namespace std;
int map[505][505];
int vis[505],val[505],dis[505],sum[505];
const int inf=999999;
int main()
{
int n,m,s,e;
cin>>n>>m>>s>>e;
int i,j;
for(i=0;i<n;i++)
cin>>val[i];
for(i=0;i<n;i++)
for(j=0;j<n;j++)
{
if(i==j)
map[i][j]=0;
else
map[i][j]=inf;
}
int x,y,z;
for(i=0;i<m;i++)
{
cin>>x>>y>>z;
map[x][y]=z;
map[y][x]=z;
}
for(i=0;i<n;i++)
{
vis[i]=0;
dis[i]=inf;
sum[i]=0;
}
dis[s]=0;
sum[s]=val[s];
int min,v;
for(i=0;i<n;i++)
{
min=inf;
v=0;
for(j=0;j<n;j++)
{
if(vis[j]==0)
{
if(min>dis[j])
{
min=dis[j];
v=j;
}
}
}
vis[v]=1;
for(j=0;j<n;j++)
{
if(vis[j]==0)
{
if(dis[j]>dis[v]+map[v][j])
{
dis[j]=dis[v]+map[v][j];
sum[j]=sum[v]+val[j];
}
else if(dis[j]==dis[v]+map[v][j])
sum[j]=max(sum[v]+val[j],sum[j]);
}
}
}
cout<<dis[e]<<" "<<sum[e]<<endl;
return 0;
}