最短路dijkstra演算法詳解:dijkstra(圖解)(詳
本人小白,如果有寫的不恰當的地方,還請大家指出,共同進步學習。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
談到最短路,對於我來講最喜歡的演算法不過floyd,無腦,簡單,好理解。但是面向oj上邊解題的方式來講,floyd的複雜度常常面對超時的可能,這也使得同樣好理解的dijkstra演算法更受到青睞。很多最短路方面的題目,我個人覺得還是看到dijkstra的題解比較多,所以我這裡詳解最短路的第一個演算法就決定是先說dijkstra了。
鎮博圖~~~~沒錯,這位學者就是狄克斯特拉。狄克斯特拉1930年5月11日生於荷蘭鹿特丹的一個知識分子家庭,在兄弟姊妹4人中排行第三。他的父親是一名化學家和發明家,曾擔任荷蘭化學會主席。他母親則是一位數學家。他成功地設計並實現了在有障礙物的兩個地點之間找出一條最短路徑的高效演算法,這個演算法被命名為“狄克斯特拉演算法”,解決了機器人學中的一個十分關鍵的問題,即運動路徑規劃問題,至今仍被廣泛應用,被認為是利用“貪心法”(greedy method)設計演算法的一個成功範例。
既然說是最短路,我們當然要有貪心的思想,這裡狄克斯特拉完美的做到了這一點。那在演算法當中,是如何利用這個貪心思想的呢?我們這裡對應一個圖來看。(圖不咋好看,輕吐槽T T。)
我們假設2是起點,想要走到終點 4,顯然我們有兩種走法,而且顯而易見,走2-> 1-> 4這條路是最短的。我們不希望走2->4這條路。我們通過1這個點,能把從2->4的路徑複雜化(多走一步(多轉個彎))但是卻能夠縮短路徑耗時的操作,我們理解為鬆弛操作,我們完成dijkstra的整個演算法的過程,無非就是不斷的在鬆弛的過程。我們希望走的路徑短,那我們必然要走很多彎路- -*
我們這裡對應完整演算法的圖來理解:
我們這裡定義圖的編號為:
1 2 3
4 5 6
7 8 9
圖1:初始化的圖,其中包含邊的權值(耗時)。(這裡圖是有向圖)。
圖2:確定起點,然後向能直接走到的點走一下,記錄此時的估計值:2 6 9.。
圖3:找到距離起點最近的點,是正東邊的那個點,這時候我們耗費權值為2。然後我們進行鬆弛操作,從起點到其東南方的點直接到的權值耗費為6,但是我們通過剛剛選定的點,我們找到了到這個點更近的方式,所以這個時候我們說從起點到其東南方向的點的權值更新值從6變成了5。這個時候我們就完成了第一次鬆弛操作。
圖4:依舊是找距離起點最近的點。然後鬆弛我們發現這個時候從起點到其東南方的點的耗費權值從5又變成了4.這個時候我們完成了第二個鬆弛。
之後的方式同上:選定距離起點最近的點v。然後通過點v進行鬆弛操作。我們發現能夠通過增加走到目的地方式的複雜度(多轉彎)的方式我們能夠鬆弛掉權值,使得耗費的權值更小。
讀者請自己跑一遍上邊的圖,大概的演算法思維也就掌握了。
然後我們對應程式碼和圖一起探究:
void Dij()//我們這裡起點為1號編碼點。我們這裡的d[]表示從起點到這個點需要的權值。w[a][b]表示點a到點b這條邊的權值.
{
int i,j,k,v,tmp;
memset(vis,0,sizeof(vis));
for(i=1;i<=n;i++)
d[i]=w[1][i];//對應圖不難理解,對於起點的初始化
d[1]=0;
vis[1]=1;
for(i=1;i<=n;i++)//控制連線點的次數,例如上圖,九個點,就迴圈九次。
{
tmp=N;//這裡N表示無窮大。也就是圖上的99.
for(j=1;j<=n;j++)
{
if(tmp>d[j]&&!vis[j])
{
tmp=d[j];
v=j;
}
}//每次我們都找到距離起點最近的點v
vis[v]=1;
for(k=1;k<=n;k++)//然後進行鬆弛操作。<pre name="code" class="cpp">我們這裡的d[]表示從起點到這個點需要的權值//加以強調其含義。
{if(!vis[k])d[k]=min(d[k],d[v]+w[v][k]);}}}
上圖為鬆弛操作的效果圖。其中我加了兩個藍色的中途點,表示我們這三個點並不一定是像最初的圖那樣的三個點,他們其中是有複雜的行進路程的。這樣我們就很容易看的出來,是如何進行鬆弛操作的了~。
對於dijkstra是否能解無向圖的問題,這裡答案很明確:可以,因為無向圖就是特殊的有向圖,也可以理解為雙向圖。這裡我們對應HDU的2544進行練習:
最短路
Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 46167 Accepted Submission(s): 20348Problem Description 在每年的校賽裡,所有進入決賽的同學都會獲得一件很漂亮的t-shirt。但是每當我們的工作人員把上百件的衣服從商店運回到賽場的時候,卻是非常累的!所以現在他們想要尋找最短的從商店到賽場的路線,你可以幫助他們嗎?
Input 輸入包括多組資料。每組資料第一行是兩個整數N、M(N<=100,M<=10000),N表示成都的大街上有幾個路口,標號為1的路口是商店所在地,標號為N的路口是賽場所在地,M則表示在成都有幾條路。N=M=0表示輸入結束。接下來M行,每行包括3個整數A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A與路口B之間有一條路,我們的工作人員需要C分鐘的時間走過這條路。輸入保證至少存在1條商店到賽場的路線。
Output 對於每組輸入,輸出一行,表示工作人員從商店走到賽場的最短時間
Sample Input 2 1 1 2 3 3 3 1 2 5 2 3 5 3 1 2 0 0
Sample Output 3 2
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#define N 0x1f1f1f1f
int w[151][151];
int d[155];
int ans,vis[151];
int n,m;
void Dij()
{
int i,j,k,v,tmp;
memset(vis,0,sizeof(vis));
for(i=1;i<=n;i++)
d[i]=w[1][i];
d[1]=0;
vis[1]=1;
for(i=1;i<=n;i++)
{
tmp=N;
for(j=1;j<=n;j++)
{
if(tmp>d[j]&&!vis[j])
{
tmp=d[j];
v=j;
}
}
vis[v]=1;
for(k=1;k<=n;k++)
{
if(!vis[k])
d[k]=min(d[k],d[v]+w[v][k]);
}
}
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
if(n==0&&m==0)break;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
w[i][j]=0x1f1f1f1f;
}
}
for(int i=0;i<m;i++)
{
int a,b,dis;
scanf("%d%d%d",&a,&b,&dis);
if(w[a][b]>dis)
w[a][b]=w[b][a]=dis;
}
Dij();
printf("%d\n",d[n]);
}
}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------