我們在生活中經常會遇到這樣的問題,你要從家裏去圖書館,去健身房,去公司,那麼走哪一條路會最省時間,或者路程最短?你可能會先走xxx路,在轉到yyy路,最後轉了一圈終於走到了目的地,也可以直接開車上高速然後直達,其實這就是最短路徑的問題,哪一條路徑可以讓你走的路最少,這也是最短路徑問題的單源最短路徑問題,因爲你的起點都固定是從家裏出發。
一般情況下的最短路徑分爲三種:
1> 確定了起點的最短路徑問題
2> 確定了終點的最短路徑問題,該問題其實也就是確定了起點的最短路徑問題的逆問題
3>確定了起點和終點的問題,該問題是上述兩個問題的其中一種,即終點由多個變爲單純明確的一個
4>不確定起點和終點的全局最短路徑問題,即任意兩個地方之間的最短路徑
解決上述問題的算法有很多,比如Dijkstra算法, Floyd算法,Bellman_ford 算法,A*算法, SPFA算法等........ 這裏我們講解DIjkstra算法,該算法是解決單源最短路徑最常用的算法, 所謂的單源最短路徑是指從某一個固定的起點開始到任意一個地方的最短路徑。比如你從家裏到公司的最短路徑,從家裏到健身房的最短路徑,這些任意可以是任何你想去的地方,而起點就是那個單源:家
Dijkstra算法是從起點爲中心點,然後向外層層擴展,直到擴展到終點爲止. Dijkstra的過程是這樣,將所有的頂點分成兩個集合,第一個集合S是已經求出的最短路的頂點集合,每一次求出的最短路徑頂點都加入到該S集合中,直到所有的頂點都加入了爲止;另外一個集合是所有還未確定最短路徑的頂點集合U,算法執行時,將U中的頂點按照最短路徑長度遞增的順序加入到S中,在加入的過程中需要滿足如下的條件。每次加入最短路到S的過程中,總是保持源點v到S中各個頂點的最短路徑長度不大於從v到U中各頂點最短路徑長度。此外每個頂點對應一個距離,S中的頂點距離就是從v到此頂點的最短路徑長度,U中的頂點距離,是從v到此頂點只包含S中頂點爲中間定點的當前最短路徑。
怎麼樣的路徑纔算最短路徑,比如從A到B,最短路徑要麼是A到B的直接距離D<A, B>, 要麼是從某個多箇中間點之後的C中轉過來之後的距離和D<A,C>+D<C,B>, 所以Dijkstra就有一個狀態轉移方程:起點V0到頂點i的最短距離爲D[i] = min{D[i], D[k] + d<k, i>} 其中d<k, i>是頂點K到i的最短距離。
代碼表述如下:
#define MaxInf 0x7fffffff
#define MaxVer 105
int dst[MaxVer]; // 起點到個頂點的最短路徑
bool mark[MaxVer]; // 標記已經選取過的頂點
int map[MaxVer][MaxVer];
int dijkstra(int sv, int ver_num)
{
for (int i = 1; i <= ver_num; i++)
{
// 初始化標記和起點最短路徑
dst[i] = map[sv][i];
mark[i] = false;
}
mark[sv] = true; // 起點加入到最短路中
int index;
int min_cost;
// sv向外層擴展n-1次
for (int i = 2; i <= ver_num; i++)
{
index = -1;
min_cost = MaxInf;
// 尋找下一個最短路徑的擴展點
for(int j = 2; j <= ver_num; j++)
{
if (!mark[j] && dst[j] < min_cost)
{
// 需找剩餘沒有標記的頂點中距離最近的一個
index = j;
min_cost = dst[j];
}
}
if (index == -1 || min_cost == MaxInf)
return 0;
// 標記新找到的那個最短路徑頂點
mark[index] = true;
for(int k = 2; k <= ver_num; k++)
{
// 更新剩餘的最短路徑U集合
if (!mark[k] && min_cost + map[index][k] < dst[k])
{
dst[k] = min_cost + map[index][k];
}
}
}
return 1;
}
Dijkstra算法可以求得單源到其他任意頂點的最短距離,執行過程要遍歷所有頂點,而且每次都需要更新和查找最下一個最短的路徑頂點,所以時間複雜度爲O(N^2), 當需要你需要求得任意兩個頂點的最短路徑時,Dijkstra就需要在原有的基礎上對每一個頂點進行一次Dijkstra的求解,其時間複雜度變成了O(n^3)。不過針對此類問題我們常用的算法是Floyd弗洛伊德算法。