問題描述
假設你是一個想環游世界的窮孩子,現在好不容易攢了些錢,想要去很多城市。但是由於資金有限,你得盡量找便宜的交通方式。但有的城市從你家根本不能直達,有些城市你從家坐高鐵就能直達,有些城市從你家到那里只有飛機。現在你知道了許多城市之間的交通費,想要到目的地去,除了直達還能轉車。轉車比飛機便宜啊!所以你精打細算想看看從你家到各城市的最便宜的交通方式,這時候有一個叫Dijkstra的人告訴你一個方法,在知道你想去的各個城市之間費用的情況下,計算出從你家到這些城市的最便宜的交通費。這就是Dijkstra算法。
算法求解過程
假設一共有n個城市。
1.先用一張紙把這些城市的名字記錄下來。再把這些城市里已經相互能直達的兩個城市的費用記在賬單上。再在黑板上寫下到各個城市的最少費用,不能直達的城市之間就記成無窮大。准備好黑板擦,黑板只是個草稿,最少費用會在求解過程中實時改變。
int **Graph=new int*[n]; //賬單,在這里我用二維數組來存這些城市之間的交通費 int *dist=new int[n]; //黑板,用來記錄當前兩個城市之間的最便宜費用 int *path=new int[n]; //記錄是從哪個城市到這個城市的 bool *collected=new int[n]; //記錄城市的名字的紙,用過之后就划掉
2.先從家出發,從黑板上挑出能最便宜直達另一個城市的路徑,我們假設這個城市為A,一個小時公交車就能到。把家到A的交通費記在黑板上,因為A已經是從家到其他城市交通費里最低的了,所以這就是家到A的最便宜的交通方式。現在已經找過家了,就在紙上把家划掉。
3.現在從A出發,把從A能到達的城市找出來。依次比較家直達這些城市的費用,與從A轉站到這些城市的費用。如果從A轉站到達這些城市比直達還便宜的話,就把便宜的哪一種費用記在黑板上,把先導路徑也記上,也就是到達此城市的前一站。如到B城市最偏宜的路徑是從A轉車,就記先導路徑為A,反之記成家。
while (1) { int V = FindMinDist(collected, dist, n); if(V==-1) { break; /* 算法結束 */ } collected[V] = true; /* 收錄V */ for(int j=0; j<n; j++ ) /* 對圖中的每個頂點j */ if ( j!=V&& Graph[V][j]<INFINITY ) { //if ( Graph[V][j]<0 ) /* 若有負邊 */ //return false; /* 不能正確解決,返回錯誤標記 */ /* 若收錄V使得dist[j]變小 */ if ( dist[V]+Graph[V][j] < dist[j] ) { dist[j] = dist[V]+Graph[V][j]; /* 更新dist[j] */ path[j]=V;/*更新路徑*/ } } } /* while結束*/ }
4.把A能到達的所有城市全布遍歷,經過3中的比較之后,就在紙上把A划去。然后從黑板上挑出除A外下一個最便宜的路徑,重復3中的操作。一直到紙上所有的城市都被划去。
5.現在黑板上所記錄的就是家到各城市的最便宜交通費了。但是由於每個城市只記錄了到達自己的前一站,所以如果想知道整條路的話,需要通過自己的前一站去找這個站的前面的站,一直遍歷到家,才能知道全部路徑。
使用范圍
在有向有權中,找到由一個頂點 V0 到其余各點的最短路徑長度
整體代碼實現(下面用一個具體題目來體現)
題目:迪傑斯特拉最短路徑算法(此題中沒有用到路徑,但我還是把用法寫上去了)
題目描述
在帶權有向圖G中,給定一個源點v,求從v到G中的其余各頂點的最短路徑問題,叫做單源點的最短路徑問題。
在常用的單源點最短路徑算法中,迪傑斯特拉算法是最為常用的一種,是一種按照路徑長度遞增的次序產生最短路徑的算法。
在本題中,讀入一個有向圖的帶權鄰接矩陣(即數組表示),建立有向圖並按照以上描述中的算法求出源點至每一個其它頂點的最短路徑長度。
輸入
輸入的第一行包含2個正整數n和s,表示圖中共有n個頂點,且源點為s。其中n不超過50,s小於n。
以后的n行中每行有n個用空格隔開的整數。對於第i行的第j個整數,如果大於0,則表示第i個頂點有指向第j個頂點的有向邊,且權值為對應的整數值;如果這個整數為0,則表示沒有i指向j的有向邊。當i和j相等的時候,保證對應的整數為0。
輸出
只有一行,共有n-1個整數,表示源點至其它每一個頂點的最短路徑長度。如果不存在從源點至相應頂點的路徑,輸出-1。
請注意行尾輸出換行。
樣例輸入
4 1
0 3 0 1
0 0 4 0
2 0 0 0
0 0 1 0
樣例輸出
6 4 7
#include<iostream> #include<limits.h> #define INFINITY INT_MAX using namespace std; int FindMinDist(bool *collected,int *dist,int n) { /* 返回未被收錄頂點中dist最小者 */ int MinV; int MinDist = INFINITY; for (int i=0; i<n; i++) { if ( collected[i]==false && dist[i]<MinDist) { /* 若i未被收錄,且dist[i]更小 */ MinDist = dist[i]; /* 更新最小距離 */ MinV = i; /* 更新對應頂點 */ } } if (MinDist < INFINITY) /* 若找到最小dist */ return MinV; /* 返回對應的頂點下標 */ else return -1; /* 若這樣的頂點不存在,返回錯誤標記 */ } void Dijkstra(int **Graph,int start,int *dist,bool *collected,int n,int *path) { for (int i=0; i<n; i++ ) { dist[i] = Graph[start][i]; if(dist<INFINITY) path[i]=start; else path[i]=-1; } /* 先將起點收入集合 */ collected[start]= true; //收錄設為0 while (1) { int V = FindMinDist(collected, dist, n); if(V==-1) { break; /* 算法結束 */ } collected[V] = true; /* 收錄V */ for(int j=0; j<n; j++ ) /* 對圖中的每個頂點j */ if ( j!=V&& Graph[V][j]<INFINITY ) { // if ( Graph[V][j]<0 ) /* 若有負邊(但本題不涉及負邊,所以不用) */ //return ; /* 不能正確解決,返回 */ /* 若收錄V使得dist[j]變小 */ if ( dist[V]+Graph[V][j] < dist[j] ) { dist[j] = dist[V]+Graph[V][j]; /* 更新dist[j] */ path[j]=V;/*更新路徑*/ } } } /* while結束*/ } int main() { int n,s; cin >> n >> s; int **Graph=new int*[n]; int *dist=new int[n]; //原點到v的最短路徑 int *path=new int[n]; bool *collected=new bool[n]; for(int i=0;i<n;i++) { Graph[i]=new int[n]; //初始化圖 for(int j=0;j<n;j++) { cin>>Graph[i][j]; if(Graph[i][j]==0) Graph[i][j]=INFINITY; //不能到達或者是自己到自己就設為無線大 } collected[i]=false; } Dijkstra(Graph,s,dist,collected,n,path); for(int i=0;i<n;i++) //輸出源點至其它每一個頂點的最短路徑長度 { if(i==s) continue; if(dist[i]==INFINITY) cout<<-1<<" "; else cout<<dist[i]<<" "; } cout<<endl; return 0; }