動態規划法解多段圖最短路徑問題


動態規划法

動態規划法將待求解問題分解成若干個相互重疊的子問題,每個子問題對應決策過程的一個階段,一般來說,子問題的重疊關系表現在對給定問題求解的遞推關系稱為動態規划函數中,將子問題的解求解一次並填入表中,當需要再次求解此子問題時,可以通過查表獲得該子問題的解,從而避免了大量重復計算。具體的動態規划法多種多樣,但都具有相同的填表形式。一般來說,動態規划法的求解過程由以下三個階段組成:

  1. 划分子問題:將原問題分解為若干個子問題,每個子問題對應一個決策階段,並且子問題之間具有重疊關系。
  2. 確定動態規划函數:根據子問題之間的重疊關系找到子問題滿足的遞推關系式即動態規划函數,這是動態規划法的關鍵。
  3. 填寫表格:設計表格,以自底向上的方式計算各個子問題的解並填表,實現動態規划過程。

上述動態規划過程可以求得問題的最優值即目標函數的極值,如果要求出具體的最優解,通常在動態規划過程中記錄必要的信息,再根據最優決策序列構造最優解。

多段圖最短路徑問題

設圖 G =(V,E)是一個帶權有向圖,如果把頂點集合 V 划分成 k 個互不相交的子集 Vi(2<=k<=n,1<=i<=k),使得 E 中的任何一條邊 <u,v>,必有 u∈Vi, v∈Vi + m(1<=i<k, 1<i+m<=k),則稱圖 G 為多段圖,稱 s∈V1 為源點,t∈Vk 為終點。多段圖的最短路徑問題為從源點到終點的最小代價路徑。

問題分析

根據多段圖的性質,我們可以將這種特殊的圖結構划分為多個子集,例如如圖所示的多段圖就可以分成 5 個子集,在圖中以 5 種不同顏色來表示。可以明顯地看到想要到達某一個子集的頂點,就必須從上一個相鄰頂點集的頂點出發,不相鄰的子集之間不存在可達的邊。
針對這個特性可以推導出解決問題的方法,例如我想要到達頂點 10,那就必須要先到達頂點 8 或者頂點 9。換句話說,到達頂點 10 的最短距離就是在到達頂點8的最短距離 d(1,8) 加上邊 (8,10) 的權重,和到達頂點 9 的最短距離 d(1,9) 加上邊 (9,10) 的權重中取最小值。因為不相鄰的頂點集之間不存在邊,所以到達頂點 10 的方式有且僅有上述 2 種。設 C 為某條邊的權重,d(m,n) 為從點 m 到點 n 的最短距離,則使用數學語言的描述如下:

再看一個例子,假設要分析到達頂點 8 的最短距離,則只有 3 種情況。即到達頂點 5 的最短距離 d(1,5) 加上邊 (5,8) 的權重,和到達頂點 6 的最短距離 d(1,6) 加上邊 (6,8) 的權重,和到達頂點 7 的最短距離 d(1,7) 加上邊 (7,8) 的權重三者之間取最小值。使用數學語言的描述如下:

根據上面 2 個例子的論述,我們可以把情況從特殊推廣到一般情況,設 Cuv 為多段圖有向邊 <u,v> 的權值,源點 s 到終點 v 的最短路徑長為 d(s,v),終點為 t,則可以得到該問題的狀態轉移方程為:

最優子結構證明

設一個多段圖有且僅有一個起點 S,有且僅有一個終點 T,S->S1->S2->…Sn->T 為從起點 S 到終點 T 的最短路徑。設 S->S1 的開銷已經求出,則從起點 S 到終點 T 的最小開銷的求解將轉換為對點 S1 到終點 T 的最小開銷進行求解。
假設 S1->S2->S3…Sn->T 不是點 S1 到終點 T 的最短路徑,則必然存在另一條路徑 S1->R1->R2…Rn->T 的開銷小於 S1->S2->S3…Sn->T 的路徑開銷,進而推出起點 S 到終點 T 的最短路徑為 S->S1->R1->R2…Rn->T。然而已知路徑 S->S1->S2->…Sn->T 為起點 S 到終點 T 的最短路徑,不可能存在其他路徑的總開銷比該路徑的開銷還要小,產生了矛盾,因此多段圖的最短路徑問題滿足最優子結構。

問題求解

例如上述例子中的多段圖,可以建立圖的鄰接矩陣 topography.edges[MAXV][MAXV] 為:

需要一個輔助結構:一維數組 cost[MAXV] 存儲到每個頂點的最小開銷。例如我們已經求出了到達頂點 1~9 的最小開銷如下:

則根據狀態轉移方程,可以得出從頂點 1 到頂點 10 的最小開銷為:

如果我們要找出最短路徑,還需要一個一維數組 path[MAXV],用於保存每個頂點的前驅頂點。

找到最短路徑的方式為從 path[10] 開始依次訪問對應的頂點,訪問的次序即為所求的最短路徑,直到訪問了起點為結束。

程序編寫

首先定義圖結構的結構體,使用鄰接矩陣來存儲:

typedef struct    //圖的定義
{
      int edges[MAXV][MAXV];    //鄰接矩陣
      int n;    //頂點數
} MGraph;

定義求解問題需要的輔助結構:

MGraph topography;    //保存城市關系的鄰接矩陣 
int path[MAXV] = {};    //保存到該頂點的最短路徑對應的前驅 
int min_cost[MAXV] = {};    //保存到每個頂點的最短路徑長 

要根據測試樣例數據建立多段圖的鄰接矩陣:

MGraph CreateMGraph(int num)    //建圖 
{
	MGraph topography;
	int n;
	int point1, point2;
	int value;
	
	//初始化邊為不存在 
	for(int i = 1; i <= num; i++)
	{
		for(int j = 1; j <= num; j++)
		{
			topography.edges[i][j] = 0;
		}
	}
	cout << "請輸入邊數:";
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> point1 >> point2 >> value;
		topography.edges[point1][point2] = value;
	}
	cout << "\n建立的鄰接矩陣為:" << endl; 
	for(int i = 1; i <= num; i++)
	{
		for(int j = 1; j <= num; j++)
		{
			printf("%2d ",topography.edges[i][j]);
		}
		cout << endl;
	}
	cout << endl;
	topography.n = num;
	return topography;
}

根據狀態轉移方程求解問題,最后輸出最短路徑及其開銷:

int main()
{
	int cities_num = 0;    //城市數量 
	int a_cost;    //當前路徑的開銷
	int pre;
	
	cout << "城市數量為:"; 
	cin >> cities_num;
	//建圖
	topography = CreateMGraph(cities_num);
        //初始化路徑開銷
	min_cost[1] = 0; 
	for(int i = 2; i <= topography.n; i++) 
	{
		min_cost[i] = 99999;
	}
	//依次計算到達所有點的最短路徑 
	for(int i = 2; i <= cities_num; i++)
	{
		//遍歷之前的所有點,計算到達該點的最短路徑 
		for(int j = 1; j < i; j++)
		{
			if(topography.edges[j][i] != 0)    //若路徑存在 
			{
				a_cost =  min_cost[j] + topography.edges[j][i];
				if(a_cost < min_cost[i])    //更新最短路徑長 
				{
					min_cost[i] = a_cost;
					path[i] = j;    //記錄前驅頂點 
				}
			}
		}
	}
	//輸出到所有頂點的最短路徑 
	for(int i = 1; i <= cities_num; i++)
	{
		cout << "到頂點" << i << "的最小開銷為:" << min_cost[i] << ",路徑:" << i;
		pre = i;
		while(path[pre])
		{
			cout << "<-" << path[pre];
			pre = path[pre];
		}
		cout << endl;
	}
        return 0;
}

測試樣例

樣例一

輸入數據

10
18
1 2 4
1 3 2
1 4 3
2 5 9
2 6 8
3 5 6
3 6 7
3 7 8
4 6 4
4 7 7
5 8 5
5 9 6
6 8 8
6 9 6
7 8 6
7 9 5
8 10 7
9 10 3

輸出數據

樣例二

輸入數據

16
30
1 2 5
1 3 3
2 4 1
2 5 3
2 6 6
3 5 8
3 6 7
3 7 6
4 8 6
4 9 8
5 8 3
5 9 5
6 9 3
6 10 3
7 9 8
7 10 4
8 11 2
8 12 2
9 12 1
9 13 2
10 12 3
10 13 3
11 14 3
11 15 5
12 14 5
12 15 2
13 14 6
13 15 6
14 16 4
15 16 3

輸出數據

樣例三

輸入數據

12
21
1 2 7
1 3 6
1 4 4
1 5 2
2 6 4
2 7 2
2 8 1
3 6 2
3 7 7
4 8 11
5 7 11
5 8 8
6 9 6
6 10 5
7 9 4
7 10 3
8 10 5
8 11 6
9 12 4
10 12 2
11 12 5 

輸出數據

算法分析

算法的時間復雜度主要由兩部分組成:第一部分是依次計算從源點到各個頂點的最短路徑長度,由兩層嵌套的循環組成,外層循環執行 n-1 次,內層循環對所有入邊進行計算,並且在所有循環中,每條入邊只計算一次。假定圖的邊數為 m,則時間性能是 O(m)。第二部分是輸出最短路徑經過的頂點,設多段圖划分為 k 段,其時間性能是 O(k)。綜上所述,時間復雜度為 O(m+k)

參考資料

《算法設計與分析(第二版)》——王紅梅,胡明 編著,清華大學出版社


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM