最短路徑——Dijkstra演算法和Floyd演算法

聽聞__發表於2018-05-11

一、最短路徑概念

        圖中一個頂點到另一個頂點可能存在多條路徑,每條路徑所經過邊的數量可能不同,每條路徑所經過邊的權值也可能不同,我們把花費最小的路徑成為兩個頂點的最短路徑。

        最短路徑相關的兩種常用演算法:迪克斯特拉(Dijstra)演算法和弗洛伊德(Floyd)演算法。Dijstra演算法用於快速求得圖中一個頂點到其他所有頂點的最短距離和路徑,Floyd演算法用於求圖中每對頂點的最短路徑。

二、應用

        最短路徑問題已經應用在了諸多領域,如地圖導航,公路建設,路由器定址等等。

三、Dijstra演算法和Floyd演算法的具體實現

(1)Dijstra演算法

        Dijstra演算法的基本思想是:將圖中的頂點集合分成兩組,第1組為已求出最短路徑的頂點集合S,第2組為其餘未確定最短路徑的頂點集合U。然後從一個已知的頂點k開始,尋找離k最近的頂點imin,然後把頂點imin加入到第1組頂點集合S中,如果以頂點imin作為中間點到其他頂點的距離更短,則設定imin最為路徑的中間點,並更新k到其他頂點的最短距離,重複尋找最近頂點imin直至所有頂點都加入到集合S中。

        其執行步驟如下:

將圖中的頂點集合分成兩組,第1組為已求出最短路徑的頂點集合S,第2組為其餘未確定最短路徑的頂點集合U。

②將已知的起始點k加入S中,並初始化k到其他頂點的最短距離為有向圖相關邊的權值(若不存在邊,設定距離為無窮大)。

③重複步驟④,直至所有頂點都加入到集合S中。

尋找離k最近的頂點imin,然後把頂點imin加入到第1組頂點集合S中,如果以頂點imin作為中間點到其他頂點的距離更短,則設定imin最為路徑的中間點,並更新k到其他頂點的最短距離。

        C++程式碼實現:

        圖的鄰接矩陣宣告:

#define N 100

typedef char ElemType;

/*
圖的鄰接矩陣宣告
*/
typedef struct _MGraph
{
	int edges[N][N]; //邊集合
	int n; //頂點數
}MGraph;

        Dijstra演算法:

/*
迪克斯特拉演算法
g儲存有向圖的邊
k代表出發的頂點
path[i]儲存第i個頂點的上一個頂點
dis[i]儲存從k出發到頂點i的最短距離
*/
void Dijkstra(MGraph &g, int k, int path[], int dis[])
{
	int* visited = new int[g.n](); //儲存頂點是否被訪問過,初始化為0
	for (int i = 0; i < g.n; i++) 
	{
		dis[i] = g.edges[k][i]; //初始化最短距離陣列
		path[i] = k; //初始化路徑陣列
	}
	visited[k] = 1;
	dis[k] = 0;
	for (int cnt = 1; cnt < g.n; cnt++) //迴圈n-1次
	{
		int imin = -1; //儲存最短邊的下標
		for (int i = 0; i < g.n; i++) //尋找沒訪問過的最短邊
		{
			if (!visited[i] && (imin == -1 || dis[i] < dis[imin])) 
				imin = i;
		}
		visited[imin] = 1;
		for (int i = 0; i < g.n; i++) //如果新的頂點到其他頂點的距離更短,更新最短距離和路徑
		{
			if (!visited[i] && dis[imin] + g.edges[imin][i] < dis[i])
			{
				dis[i] = dis[imin] + g.edges[imin][i];
				path[i] = imin;
			}
		}
	}
	delete[] visited; //記得釋放記憶體
}

        根據Dijstra演算法生成的path陣列輸出路徑:

/*
輸出從開始頂點到頂點k的最短路徑
*/
void DisplayPath(int k, int path[])
{
	stack<int> s;
	while (path[k] != k)
	{
		s.push(k);
		k = path[k];
	}
	s.push(k);
	int cnt = 0;
	while (!s.empty())
	{
		if (cnt++ > 0) cout << "->";
		cout << s.top();
		s.pop();
	}
	cout << endl;
}

        Dijstra演算法包含了兩重for迴圈,其時間複雜度為O(n²)。

(2)Floyd演算法

        Floyd演算法的基本思想是:用一個二維陣列dis來儲存每對頂點之間的最短路徑長度,即dis[i][j]表示從頂點i到頂點j的最短路徑長度,dis陣列初始化為圖的鄰接矩陣陣列dis[i][j] = g.edges[i][j]。從頂點k=0開始,將k作為中間節點,如果頂點i以k最為中間節點到達頂點j的距離更短,則設定k為中間節點,並更新i到j的最短路徑。將k的值加1,重複選取中間節點直到所有頂點都被假設為中間節點為止。

        其執行步驟如下:

①用一個二維陣列dis來儲存每對頂點之間的最短路徑長度,即dis[i][j]表示從頂點i到頂點j的最短路徑長度,dis陣列初始化為圖的鄰接矩陣陣列dis[i][j] = g.edges[i][j]。

②從頂點k=0開始,將k假設為中間節點,重複步驟③直至所有頂點都被假設為中間節點為止。

③如果頂點i以k最為中間節點到達頂點j的距離更短,則設定k為中間節點,並更新i到j的最短路徑。

        C++程式碼實現:

        Floyd演算法:

/*
弗洛伊德演算法
g儲存有向圖的邊
dis[i][j]儲存頂點i到頂點j的最短距離長度
path[i][j]儲存頂點j的上一個頂點
*/
void Floyd(MGraph& g, int dis[][N], int path[][N])
{
	for (int i = 0; i < g.n; i++)
	{
		for (int j = 0; j < g.n; j++)
		{
			dis[i][j] = (i == j ? 0 : g.edges[i][j]); //初始化距離陣列
			path[i][j] = i; //初始化路徑陣列
		}
	}
	for (int k = 0; k < g.n; k++)
	{
		for (int i = 0; i < g.n; i++)
		{
			for (int j = 0; j < g.n; j++)
			{
				if (dis[i][k] + dis[k][j] < dis[i][j]) //如果以K為中間點距離更短,更新距離陣列和路徑陣列
				{
					dis[i][j] = dis[i][k] + dis[k][j];
					path[i][j] = path[k][j];
				}
			}
		}
	}
}

        根據Floyd生成的path陣列輸出最短路徑:

/*
輸出每對頂點的最短路徑
*/
void DisplayPath(int n, int path[][N], int dis[][N])
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			cout << i << "-" << j << "最短路徑長度:" << dis[i][j] << " 最短路徑:";
			DisplayPath(j, path[i]);
		}
	}
}

        Floyd演算法包含三重for迴圈,其時間複雜度為O(n³),相當於以每個頂點作為源點呼叫Dijstra演算法的時間複雜度,但Floyd演算法程式碼實現簡單。

四、測試

        問題:

        輸入一個有向圖,求頂點0到其他頂點的最短路徑長度和最短路徑,以及每對頂點的最短路徑長度和最短路徑。

        樣例輸入:

        4 8
        0 1 5
        0 3 7
        1 2 4
        1 3 2
        2 0 3
        2 1 3
        2 3 2

        3 2 1

        樣例輸出:

        0-0最短路徑長度:0 最短路徑:0
        0-1最短路徑長度:5 最短路徑:0->1
        0-2最短路徑長度:8 最短路徑:0->3->2
        0-3最短路徑長度:7 最短路徑:0->3
        0-0最短路徑長度:0 最短路徑:0
        0-1最短路徑長度:5 最短路徑:0->1
        0-2最短路徑長度:8 最短路徑:0->3->2
        0-3最短路徑長度:7 最短路徑:0->3
        1-0最短路徑長度:6 最短路徑:1->3->2->0
        1-1最短路徑長度:0 最短路徑:1
        1-2最短路徑長度:3 最短路徑:1->3->2
        1-3最短路徑長度:2 最短路徑:1->3
        2-0最短路徑長度:3 最短路徑:2->0
        2-1最短路徑長度:3 最短路徑:2->1
        2-2最短路徑長度:0 最短路徑:2
        2-3最短路徑長度:2 最短路徑:2->3
        3-0最短路徑長度:4 最短路徑:3->2->0
        3-1最短路徑長度:4 最短路徑:3->2->1
        3-2最短路徑長度:1 最短路徑:3->2
        3-3最短路徑長度:0 最短路徑:3

#include <iostream>
#include <stack>
using namespace std;

#define N 100

typedef char ElemType;

/*
圖的鄰接矩陣宣告
*/
typedef struct _MGraph
{
	int edges[N][N]; //邊集合
	int n; //頂點數
}MGraph;

/*
迪克斯特拉演算法
g儲存有向圖的邊
k代表出發的頂點
path[i]儲存第i個頂點的上一個頂點
dis[i]儲存從k出發到頂點i的最短距離
*/
void Dijkstra(MGraph &g, int k, int path[], int dis[])
{
	int* visited = new int[g.n](); //儲存頂點是否被訪問過,初始化為0
	for (int i = 0; i < g.n; i++) 
	{
		dis[i] = g.edges[k][i]; //初始化最短距離陣列
		path[i] = k; //初始化路徑陣列
	}
	visited[k] = 1;
	dis[k] = 0;
	for (int cnt = 1; cnt < g.n; cnt++) //迴圈n-1次
	{
		int imin = -1; //儲存最短邊的下標
		for (int i = 0; i < g.n; i++) //尋找沒訪問過的最短邊
		{
			if (!visited[i] && (imin == -1 || dis[i] < dis[imin])) 
				imin = i;
		}
		visited[imin] = 1;
		for (int i = 0; i < g.n; i++) //如果新的頂點到其他頂點的距離更短,更新最短距離和路徑
		{
			if (!visited[i] && dis[imin] + g.edges[imin][i] < dis[i])
			{
				dis[i] = dis[imin] + g.edges[imin][i];
				path[i] = imin;
			}
		}
	}
	delete[] visited; //記得釋放記憶體
}

/*
輸出從開始頂點到頂點k的最短路徑
*/
void DisplayPath(int k, int path[])
{
	stack<int> s;
	while (path[k] != k)
	{
		s.push(k);
		k = path[k];
	}
	s.push(k);
	int cnt = 0;
	while (!s.empty())
	{
		if(cnt++ > 0) cout << "->";
		cout << s.top();
		s.pop();
	}
	cout << endl;
}

/*
弗洛伊德演算法
g儲存有向圖的邊
dis[i][j]儲存頂點i到頂點j的最短距離長度
path[i][j]儲存頂點j的上一個頂點
*/
void Floyd(MGraph& g, int dis[][N], int path[][N])
{
	for (int i = 0; i < g.n; i++)
	{
		for (int j = 0; j < g.n; j++)
		{
			dis[i][j] = (i == j ? 0 : g.edges[i][j]); //初始化距離陣列
			path[i][j] = i; //初始化路徑陣列
		}
	}
	for (int k = 0; k < g.n; k++)
	{
		for (int i = 0; i < g.n; i++)
		{
			for (int j = 0; j < g.n; j++)
			{
				if (dis[i][k] + dis[k][j] < dis[i][j]) //如果以K為中間點距離更短,更新距離陣列和路徑陣列
				{
					dis[i][j] = dis[i][k] + dis[k][j];
					path[i][j] = path[k][j];
				}
			}
		}
	}
}

/*
輸出每對頂點的最短路徑
*/
void DisplayPath(int n, int path[][N], int dis[][N])
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			cout << i << "-" << j << "最短路徑長度:" << dis[i][j] << " 最短路徑:";
			DisplayPath(j, path[i]);
		}
	}
}

int main()
{
	MGraph g;
	while (cin >> g.n)
	{
		for (int i = 0; i < g.n; i++)
			for (int j = 0; j < g.n; j++)
				g.edges[i][j] = INT16_MAX;
		int m, u, v, w;
		cin >> m;
		while (m-- > 0)
		{
			cin >> u >> v >> w;
			g.edges[u][v] = w;
		}
		//Dijkstra
		{
			int* path = new int[g.n];
			int* dis = new int[g.n];
			Dijkstra(g, 0, path, dis);
			for (int i = 0; i < g.n; i++)
			{
				cout << 0 << "-" << i << "最短路徑長度:" << dis[i] << " 最短路徑:";
				DisplayPath(i, path);
			}
			delete[] path, dis;
		}
		//Floyd
		{
			int path[N][N], dis[N][N];
			Floyd(g, dis, path);
			DisplayPath(g.n, path, dis);
		}
	}
	return 0;
}

參考文獻

[1] 李春葆.資料結構教程.清華大學出版社,2013.

相關文章