单源最短路径---Dijkstra算法
有這樣一道題:在一個圖(如圖所示)中,一共有四個點:1 2 3 4
這四個點之間各有相連,且每條邊都有自己的權值。現在小明在點1上,
他想要到3去,請問最短路徑是多少。
很容易得到該圖的鄰接矩陣。我們建立一個二維數組a。a[i][j],i表示
起點,為行,j表示終點,為列。將相應的權值傳入其中,如果從一個
點到另一個點不通,就認為其權值為無限
例如(1-》2)為2,則a[1][2]=2;
而因為2到1不通,就令a[2][1]=∞;
另外a[1][1]之類的起點終點相同的都設為了0;
對這種求點i到點j的最短路徑的問題,很難直接求得。通常是要多求一
些多余的量才能得到結果。因為我們必須要做好遍歷全圖的準備才能比
較或是尋找出其最短路徑。
這時候我們來介紹兩種算法:
- 一種是Dijkstra算法單源最短路徑算法;
- 一種是Floyd多元路徑最短算法;
今天只說單源最短路徑算法
所謂單源,即求從一個點出發,到其他各點的最短路徑,也就是說
如果這個圖有n個點,我們要求n-1個路徑。
對一個圖G來說,它的點集為V,我們要做的就是求出從起點v到V中其
余各點的最短路徑。
首先介紹單源最短路徑的核心算法:
起點是v,我們知道在整個圖中,以v為起點的路徑有很多條,有長的。有短的;有的簡單,只有一段弧,只有兩個點:起點。終點。有的復雜,有好幾段弧,起點終點之間隔了好幾個節點。
我們要做的就是先找到所有這些從v引出的路徑中的最短的那一條(v,…,j1)。
然后通過某種算法(下文中將會介紹)再找出僅長于最短路徑的次短路徑(v,…,j2),找到以后,(注意:我們把每一次找到的次短路徑的終點記錄下來,如果下一次找尋的時候遇到以這些點為終點的路徑就忽略掉),再找下一條次短路徑,再找,再找……
這里有一個辨析:
這里,如果我們稱圖中所有以v為起點 以j為終點的路徑為j族,那么我們每找到一條次短路徑 (v,…,j),這條路徑(v,…,j)一定是j族中最短的那條。
為什么呢?按照我們的找法,我們實際上是把整張圖里的所有以v為起點的路徑按照從短到長的順序排列起來,然后從第一條路徑開始往后檢索,如果我們從前到后查找時第一次遇到了以j為終點的路徑,記為路徑1,接下來往后,我們遇到的每一條屬于j族的路徑都忽略,從而我們得到了之前的序列。顯然路徑1就是j族中最短的那一條,也是我們的目標路徑之一。
我們 所要求的是點v到其余各點的最短路徑,其余各點有n-1個,也就是說,我們有n-1個終點,對應n-1個族。對每個族,都有一個對應的最短路徑。我們要做的就是求出每個族的最短路徑。
之前說到,我們要把每一次找到的次短路徑的終點記錄下來,下一次遇到的時候就忽略掉,這可以保證最終的得到的序列都屬于不同的族,我們可以建立一個點集S,每找到一條次短路徑,就將其終點并入集合S中,下一次找尋路徑的時候拿終點與點集S對比,決定是否保留。
就這樣,每一次我們都找到一個以j為終點的最短路徑,而每一次的終點j又都不同,當找了n-1次時,就完成了單源最短路徑查找。
我們已經了解完了整體的思路,所以現在的關鍵性問題就是如何完成對次短路徑的查找。也就是上文中提到的某種算法,它到底是什么呢?
第一步:
一個圖中有許多條路徑,我們現在在圖G中找到以v為起點的最短的那一條路徑(v,j)。顯然我們可以很容易的的發現,這條最短路徑一定是與起點v直接相連的弧,j是v的鄰結點。如圖所示;
第二步:
接下來,我們來找次短路徑,也就是以v為起點的下一條最短的路徑。如圖所示我們會發現,次短路徑要么是弧(v,k)(注:k是v相鄰的點,除j外),要么是弧(v,j,k)(k是j緊鄰的點)。
這樣,我們通過比較可以求出最短的那一條路徑(v,k);
然后我們就會猜想,如果按照第二步的做法一直重復下去,不就能依次找到次短路徑了嗎?
但是,事實上,當我們嘗試之后會發現,隨著不斷地重復查找次短路徑的過程,我們不能單純的像第二次查找那樣,因為每一次的查找都會衍生很多分支。使得查找變得復雜。
怎么解決這個問題呢?為了方便理解我引入了一個觀測域的概念。
首先看一些定義:
- 點集V 圖中所有點的集合
- 點集S 已經找到相應最短路徑的終點集合
- 數組D[n] 存儲觀測域內能觀測到的最短路徑,算上起點一共n個數值 。比如D[k]對應在觀測域中能觀測到的,k族中的最短路徑。
- 鄰接矩陣a[n][n] 存儲著相應的權值
如圖所示:剛開始時,我立足于v點,觀測域為v點的四周,即v的所有鄰接點。由鄰接矩陣a,更新數組D。此時D中的數為(v,k)的權值(k為v的鄰接點)。
隨后,我在我觀測域中找尋最短的那一條路徑(v,j)也就是查找數組D中最小的數,并將j收入集合S中。
如圖所示:現在我想找次短路徑,現在我清楚,這條次短路徑要么是(v,k)(k為觀測域中的點,但不屬于S)的最短邊,要么是通過j點的弧(v,j,k)(k為j的鄰接點,但不屬于S)的最短邊。
我們干脆將j的鄰接點全都納入觀測域,同時更新數組D,通過數組D找出次短邊(v,j),再將j壓入S中。
不斷重復該步驟,直到所有的點都入了S,就完成了查找,這時數組D
D[k]就是從v到k的最短路徑的長度。如果你想知道具體的路徑時只需加個棧就行了。
所以完整的步驟是這樣的:
第一步:
初始化點集S,將起點v收入S中。初始化數組D:D[k]=a[v][k];第二步:找尋次短路徑。即查找數組D找出觀測域中最短路徑(v,j):D[j]=min(D[k]|k不屬于S)。將j壓入點集S中
第三步:將j的鄰接點并入觀測域,即用j的鄰接點更新數組D:
如果D[k]>D[j]+a[j][k] (k為j鄰接點,k不屬于S)令D[k]=D[j]+a[j][k]如果D[k]>D[j]+a[j][k] (k為j鄰接點,k不屬于S)就不做操作
然后不斷重復第二步和第三步直到找到全部節點為止。
具體實現為:
#include<iostream> using namespace std; #define BUTONG 1000000 // 無限大為不通 #define NUM 4 //d點數為4 int a[NUM][NUM]={0,2,6,4,BUTONG,0,3,BUTONG,7,BUTONG,0,1,5,BUTONG,12,0}; #define OK 1 #define ERROR 0 typedef int status;bool finish(bool *S,int n) //是否完成 找尋 {for(int i=0;i<n;i++){if(!S[i])return false;}return true; }status djs(int n,int t)//a 為數組 n 為點的個數,v為起始點 {//初始化數組vint D[n];for(int i=0;i<n;i++)D[i]=a[t][i];D[t]=0;//初始化已訪問數集S;bool S[n];for(int i=0;i<n;i++)S[i]=false;S[t]=true;int j=0;int min=BUTONG;while (!finish(S,n)){ j=0;min=BUTONG;for(int i=0;i<n;i++) //找到觀測域中最短路徑(v,j) {if(S[i]) continue;if(min>D[i]) {min=D[i];j=i;} } S[j]=true; //將j納入點集S中 for(int i=0;i<n;i++) //更新觀測域 {if(S[i]) continue;if(D[i]>D[j]+a[j][i])D[i]=D[j]+a[j][i]; }}for(int i=0;i<n;i++) //輸出 {printf("最短路徑是(v,%d)長度是%d\n",i,D[i]);}return OK; } int main() {djs(NUM,1);return 0;}總結
以上是生活随笔為你收集整理的单源最短路径---Dijkstra算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSDN训练第一周
- 下一篇: 1072: 花生采摘