1. 程式人生 > >資料結構與算法系列----多源最短路徑(Floyd-Warshall演算法)

資料結構與算法系列----多源最短路徑(Floyd-Warshall演算法)

任意兩點最短路徑被稱為多源最短路徑,即給定任意兩個點,一個出發點,一個到達點,求這兩個點的之間的最短路徑,就是任意兩點最短路徑問題,多源最短路徑,而Floyd-Warshall演算法最簡單,只有5行程式碼,即可解決這個問題。


上圖中有4個城市8條公路,公路上的數字表示這條公路的長短。請注意這些公路是單向的。我們現在需要求任意兩個城市之間的最短路程,也就是求任意兩個點之間的最短路徑。這個問題被稱為“多源最短路徑”問題。


現在需要一個鄰接矩陣來儲存圖的資訊,在這裡我們可以用一個4*4的矩陣(二維陣列e)來儲存。比如1號城市到2號城市的路程為2,則設e[1][2]的值為2。2號城市無法到達4號城市,則設定e[2][4]的值為∞。另外此處約定一個城市自己是到自己的也是0,例如e[1][1]為0,具體如下:


我們來想一想,根據我們以往的經驗,如果要讓任意兩點(例如從頂點a點到頂點b)之間的路程變短,只能引入第三個點(頂點k),並通過這個頂點k中轉即a->k->b,才可能縮短原來從頂點a點到頂點b的路程。那麼這個中轉的頂點k是1~n中的哪個點呢?甚至有時候不只通過一個點,而是經過兩個點或者更多點中轉會更短,即a->k1->k2->b或者a->k1->k2…->ki->…->b。比如上圖中從4號城市到3號城市(4->3)的路程e[4][3]原本是12。如果只通過1號城市中轉(4->1->3),路程將縮短為11(e[4][1]+e[1][3]=5+6=11)。其實1號城市到3號城市也可以通過2號城市中轉,使得1號到3號城市的路程縮短為5(e[1][2]+e[2][3]=2+3=5)。所以如果同時經過1號和2號兩個城市中轉的話,從4號城市到3號城市的路程會進一步縮短為10。通過這個的例子,我們發現每個頂點都有可能使得另外兩個頂點之間的路程變短。好,這個過程只需要5句程式碼來實現:

//Floyd-Warshall演算法
	for (int k = 0; k < vertexNum; k++)
		for (int i = 0; i < vertexNum; i++)
			for (int j = 0; j < vertexNum; j++)
				if (matrix[i][j]>matrix[i][k] + matrix[k][j])
					matrix[i][j] = matrix[i][k] + matrix[k][j];

如果學過單源最短路徑,那麼多源最短路徑應該是很簡單的。這裡需要注意的是上述演算法的k,i,j順序是不可以顛倒的。因為在這句演算法“ matrix[i][j] = matrix[i][k] + matrix[k][j] ”成立的一個隱藏前提是matrix[i][k]和matrix[k][j]必須是最小,因此k是必須放在第一個迴圈中。


另外一個注意點Floyd-Warshall演算法可以解決負權圖,但是不可以解決“負權迴路”(或者叫“負權環”)的圖。因為帶有“負權迴路”的圖沒有最短路。例如下面這個圖就不存在1號頂點到3號頂點的最短路徑。因為1->2->3->1->2->3->…->1->2->3這樣路徑中,每繞一次1->-2>3這樣的環,最短路就會減少1,永遠找不到最短路。其實如果一個圖中帶有“負權迴路”那麼這個圖則沒有最短路。見下圖:


下面貼上完整程式碼:

#include<iostream>  
#include<iomanip>

#define INF 10001//假設權值不大於100

using namespace std;


int matrix[10][10];//假設不超過10個頂點,鄰接矩陣儲存


int main()
{
/*

4 8

0 1 2
0 2 6
0 3 4
1 2 3
2 0 7
2 3 1
3 0 5
3 2 12

*/
	for (int i = 0; i < 10; i++)//初始化
	{
		for (int j = 0; j < 10; j++)
		{
			if (i == j)
				matrix[i][j] = 0;
			else
				matrix[i][j] = INF;
		}
	}

	int vertexNum, sideNum;//頂點數和邊數
	int x, y, w;//邊的資訊和權值

	cout << "請輸入頂點數和邊數: ";
	cin >> vertexNum >> sideNum;

	cout << "\n請輸入" << sideNum << "條邊的資訊和權值:\n";
	for (int i = 0; i < sideNum; i++)
	{
		cin >> x >> y >> w;
		matrix[x][y] = w;//有向圖
	}

	//Floyd-Warshall演算法
	for (int k = 0; k < vertexNum; k++)
		for (int i = 0; i < vertexNum; i++)
			for (int j = 0; j < vertexNum; j++)
				if (matrix[i][j]>matrix[i][k] + matrix[k][j])
					matrix[i][j] = matrix[i][k] + matrix[k][j];

	//輸出結果
	cout << "\n輸出結果是:\n";
	for (int i = 0; i < vertexNum; i++)
	{
		for (int j = 0; j < vertexNum; j++)
			cout << left << setw(4) << matrix[i][j];
		cout << endl;
	}


	
	return 0;
}

輸出結果是: