1. 程式人生 > >圖的單源最短路徑--Dijkstra演算法

圖的單源最短路徑--Dijkstra演算法

我們在生活中經常會遇到這樣的問題,你要從家裡去圖書館,去健身房,去公司,那麼走哪一條路會最省時間,或者路程最短?你可能會先走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弗洛伊德演算法。