最短路徑--dijkstra演算法、弗洛伊德(Floyd)演算法(帶路徑輸出)

ve2102388688發表於2018-06-09

####最短路徑
求最短路徑重要性不言而喻,下面直接分析兩個演算法。
分類:
1:從某個源點到其餘個點的最短路徑
迪傑斯特拉(Dijkstra)演算法
2:每一對之間的最短路徑
弗洛伊德(Floyd)演算法

####一:儲存結構
鄰接矩陣(這裡不再重複講了,請參考):
https://blog.csdn.net/weixin_39956356/article/details/80470091

相關程式碼:

//鄰接矩陣
#define INT__MAX	   65000										//最大值65535,表示兩頂點沒有聯絡
#define MAX_VERTEX_NUM 20											//最多頂點數

typedef char VertexType;
typedef int  EdgeType;


//頂點資訊
typedef struct ArcCell {
	EdgeType wight;
	
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];				//二維陣列  

																	//弧的資訊
typedef struct MGraph {
	VertexType vexs[MAX_VERTEX_NUM];								//頂點向量  
	AdjMatrix  arcs;												//鄰接矩陣  
	int vexnum, arcnum;												//圖的當前頂點數和弧數  
}MGraph;

####建立有向圖
注意一下(有向圖):G.arcs[i][j].wight = w;

相關程式碼:

//建立無向圖的鄰接矩陣  
void CreatMGraph(MGraph &G)
{
	for (int i = 0; i < G.vexnum; i++) {
		printf("Please enter %d data:", i + 1);
		scanf(" %c", &G.vexs[i]);											//輸入頂點值
	}
	for (int i = 0; i<G.vexnum; i++)
		for (int j = 0; j<G.vexnum; j++)
			G.arcs[i][j].wight = INT__MAX;									//鄰接矩陣初始化  

	VertexType v1, v2;
	EdgeType w;
	int i, j;
	printf("Please input the two data of arc and wight,for example: A C 5\n");
	for (int k = 0; k < G.arcnum; k++) {
		printf("The %d arc: ", k + 1);
		scanf(" %c", &v1);														//輸入第一個頂點
		getchar();																//把輸入的空格讀走
		v2 = getchar();															//輸入弧的第二個頂點
		scanf("%d", &w);														//輸入權值
		i = LocateVex(G, v1);													//獲取弧的第一個節點位置
		j = LocateVex(G, v2);													//獲取弧的第二個節點位置

		G.arcs[i][j].wight = w;													//把權值存放在鄰接矩陣中
	}
}

####二:迪傑斯特拉(Dijkstra)演算法,帶輸出路徑
自己將這個演算法分成兩部分:
一:最短路徑長度
二:最短路徑
####首先,我們先看最短路徑長度問題
1:這裡需要引入一個輔助陣列Dij[],每次遍歷一遍陣列得到本次陣列中最小值及所在位置。
這裡寫圖片描述
2:找到最小值,併入S集
這裡寫圖片描述
3:更新新的路徑值
判斷條件:還沒有併入S集,且有更小的路徑值
if (!isGetShortestPath[w] && (min + G.arcs[minIndex][w].wight < Dij[w]))
這裡寫圖片描述
####其次,最短路徑
我拿一個圖來講解,整個流程。不防舉A->E。它的最短路徑是A->C->E,我們分析下
1:這裡需要引入一個輔助Code陣列code[][],記錄走過的最短路徑,初始值不防賦值為’!’。
這裡寫圖片描述
2:清空E結點該行
3:把C結點路徑拷貝到E中
4:在第一個 ! ,新增尾結點E
這裡寫圖片描述

圖6及輸出:
這裡寫圖片描述
這裡寫圖片描述

補充:這裡再多說一句,動態二維陣列分配
######先分配行,後分配列

typedef char **shortestCode;										//最短路徑編碼
/******************************************************************
	**	動態分配二維陣列
	*******************************************************************/
	code = (shortestCode)malloc(G.vexnum * sizeof(char *));						//分配G.vexnum行
	for (int i = 0; i < G.vexnum; ++i) {										//為每行分配G.vexnum列
		code[i] = (char *)malloc(G.vexnum * sizeof(char));
	}

相關程式碼

/***********************************************************************************************
**							迪傑斯特拉(Dijkstra)演算法
**	v0  :任給一個起始點
** &code:最短路徑線路(這裡的&是必須的,如果沒有,相當於傳進來沒有初始化的實體,編譯器報錯)
************************************************************************************************/
void ShortestPath_DIJ(MGraph &G, char v0, shortestCode &code)
{
	int v0Index = LocateVex(G, v0);
	bool *isGetShortestPath = (bool *)malloc(G.vexnum * sizeof(bool));			//是否併入S集
	int *Dij = (int *)malloc(G.vexnum * sizeof(int));							//最短路徑和

	/******************************************************************
	**	動態分配二維陣列
	*******************************************************************/
	code = (shortestCode)malloc(G.vexnum * sizeof(char *));						//分配G.vexnum行
	for (int i = 0; i < G.vexnum; ++i) {										//為每行分配G.vexnum列
		code[i] = (char *)malloc(G.vexnum * sizeof(char));
	}
	 
	for (int i = 0; i < G.vexnum; i++) {										//初始所有點都不併入S集,把頂點的權值賦給Dij[]
		isGetShortestPath[i] = false;
		Dij[i] = G.arcs[v0Index][i].wight;

		for (int w = 0; w < G.vexnum; w++)
			code[i][w] = '!';													//設code[][]初值為!,即沒有路徑   
		if (Dij[i] < INT__MAX)													
		{
			code[i][0] = v0;													//到i最短路徑經過的第一個頂點是v0
			code[i][1] = G.vexs[i];												//到i最短路徑經過的第二個頂點是i
		}
	}

	isGetShortestPath[v0Index] = true;											//把頂點併入S集
	
	int min, minIndex;
	for (int i = 1; i < G.vexnum; i++) {
		min = INT__MAX;															//不妨把min放置為最大值
		/******************************************************************
		**	獲取Dij陣列中最小值-----這是其他還沒有併入S集 路徑上的一個子集
		**	對應的陣列位置----------為方便下面從這結點出發的弧遍歷找最小值
		*******************************************************************/
		for (int w = 0; w < G.vexnum; w++) {
			if (!isGetShortestPath[w])
				if (Dij[w] < min)
				{
					minIndex = w;
					min = Dij[w];
				}
		}

		isGetShortestPath[minIndex] = true;										//把位置為minIndex的結點併入S集

		for (int w = 0; w < G.vexnum; w++) {									//如果有更小的路徑,替換原來的路徑和最小值
			if (!isGetShortestPath[w] && (min + G.arcs[minIndex][w].wight < Dij[w]))
			{
				Dij[w] = min + G.arcs[minIndex][w].wight;						//更新Dij[]值

				for (int j = 0; j < G.vexnum; j++) {							//如果Dij[]值改變,新節點路徑清空
					code[w][j] = '!';
				}
				for (int j = 0; j < G.vexnum; j++) {
					code[w][j] = code[minIndex][j];								//把minIndex結點路徑全部拷貝到w中去
					if (code[w][j] == '!')
					{
						code[w][j] = G.vexs[w];									//在遇到第一個'!',將新節點放在後面
						break;
					}
				}
			}
		}
	}

	printf("\n迪傑斯特拉(Dijkstra)最短路徑長度與最短路徑如下所示:\n");														
	for (int i = 1; i < G.vexnum; i++) {
		if (Dij[i] != INT__MAX)
		{
			printf("從頂點 %c ---> %c 最短路徑長度為:%d\t最短路徑為:", v0, G.vexs[i], Dij[i]);
			for(int j = 0; j < G.vexnum; j++)
			{
				if(code[i][j] != '!')
					printf(" %c",code[i][j]);									//輸出最短路徑
			}
			printf("\n");
		}
		else if (Dij[i] == INT__MAX)
		{
			printf("從頂點 %c ---> %c 不可到達!!!\n", v0, G.vexs[i]);		//不存在最短路徑
		}
	}

	free(Dij);																	//釋放Dij[][]空間
	free(isGetShortestPath);													//釋放isGetShortestPath[]空間
}

####程式輸出
圖6及輸出:
這裡寫圖片描述
圖5及輸出:
這裡寫圖片描述

####三:弗洛伊德(Floyd)演算法,帶輸出路徑
和dijkstra演算法一樣,自己將這個演算法分成兩部分:
一:最短路徑長度
二:最短路徑
####首先,我們先看最短路徑長度問題
這個演算法的最短路徑長度核心程式碼只有5行
1:就是看走哪邊短!!!如果短就替換。
這裡寫圖片描述

####其次,最短路徑(弗洛伊德輸出路徑要麻煩些)
####一:路徑的建立
####1:Path[ ][ ]初始化
矩陣Path的初值則為各個邊的終點頂點-----相當於直接從v到w(上面兩種方案的第一種)
這裡寫圖片描述
####2:Path[ ][ ]逐漸儲存路徑
當且僅當通過“另外一個點”的時候,有更短路徑,即更新路徑Path[v][w],把“另外一個點”放進Path[v][w]
這裡寫圖片描述
這裡舉個例子:比如A->D,路徑是A->E->D
這裡寫圖片描述
####二:路徑的輸出
這裡主要是是一個迴圈,只到輸出字母是終點為止。
這裡寫圖片描述
不防先人為的輸出
起點
,和第一個點,剩下的交給while迴圈
這裡寫圖片描述
最後舉個例子:比如A->F,路徑是A->E->D->F
這裡寫圖片描述
需要注意的是:Floyd-Warshall演算法不能解決帶有“負權迴路”(或者叫“負權環”)的圖
問題。因為帶有“負權迴路”的圖沒有最短路。比如:
這裡寫圖片描述

相關程式碼

/*************************************************************************************************************
**							弗洛伊德(Floyd)演算法
**	核心:是一種嘗試的想法,從v到w只有兩種方案:
			1:直接從v到w,即DFloyd[v][w]
			2:經過“另外的一個點”u,從v到w,即DFloyd[v][u] + DFloyd[u][w]
		  比較兩種方案,取更小的
			1:比較	   :DFloyd[v][u] + DFloyd[u][w] < DFloyd[v][w]
			2:取更小的:DFloyd[v][w] = DFloyd[v][u] + DFloyd[u][w]
**	這裡的Path矩陣的構造很巧,關於Path矩陣程式碼僅僅只有幾行而已!!!下面分析Path矩陣
	1:矩陣Path的初值則為各個邊的終點頂點-----相當於直接從v到w(上面兩種方案的第一種)
	2:當且僅當通過“另外一個點”的時候,有更短路徑,即更新路徑Path[v][w],把“另外一個點”放進Path[v][w]裡
** 路徑輸出
	1:不防把起點(G.vexs[i]),和Path[i][j]先輸出(Path[i][j]是路徑的第一個點)
	2:迴圈輸出直到(Path[temp][j] == j)為止!!!
**	自己可能還沒有講清楚,不用擔心我在畫個圖解釋下
	請參考:https://mp.csdn.net/mdeditor/80579845
****************************************************************************************************************/
void ShortestPath_FLOYD(MGraph &G, shortestCode &Path)
{
	//最短路徑長度DFloyd[][]
	int **DFloyd = (int **)malloc(G.vexnum * sizeof(int *));					//動態二維陣列DFloyd[][],分配G.vexnum行
	for (int i = 0; i < G.vexnum; ++i) {										//為每行分配G.vexnum列
		DFloyd[i] = (int *)malloc(G.vexnum * sizeof(int));
	}

	//最短路徑Path[][]
	Path = (shortestCode)malloc(G.vexnum * sizeof(char *));						//動態二維陣列Path[][],分配G.vexnum行
	for (int i = 0; i < G.vexnum; ++i) {											//為每行分配G.vexnum列
		Path[i] = (char *)malloc(G.vexnum * sizeof(char));
	}

	for (int v = 0; v < G.vexnum; v++)
		for (int w = 0; w < G.vexnum; w++) {
			if (v == w)															//這裡把主對角線權值變為0,不然主對角線會錯,因為之前賦值的∞,舉個例子B->B,現有B->C + C->B之和肯定小於∞,所以主對角線發生錯誤
				G.arcs[v][w].wight = 0;
			DFloyd[v][w] = G.arcs[v][w].wight;									//把初始值存入DFloyd[][]
			Path[v][w] = G.vexs[w];												//矩陣Path的初值則為各個邊的終點頂點
		}
	printf("Path矩陣:\n");
	for (int i = 0; i < G.vexnum; i++) {
		for (int j = 0; j < G.vexnum; j++)
			printf("%c\t", Path[i][j]);
		printf("\n");
	}
	/******************************************************************
	**	u:經過另外一個點
	**	v:起點
	**	w:終點
	*******************************************************************/
	for (int u = 0; u < G.vexnum; u++)
		for (int v = 0; v < G.vexnum; v++)
			for (int w = 0; w < G.vexnum; w++)
				if (DFloyd[v][u] + DFloyd[u][w] < DFloyd[v][w]) {				//比較兩種方案,取更小的
					DFloyd[v][w] = DFloyd[v][u] + DFloyd[u][w];
					Path[v][w] = Path[v][u];									//更新Path矩陣
				}

	printf("\n\t\t弗洛伊德(Floyd)演算法\nD矩陣:\n");
	for (int i = 0; i < G.vexnum; i++){
		for (int j = 0; j < G.vexnum; j++) 			
			if (DFloyd[i][j] < INT__MAX)
				printf("%d\t", DFloyd[i][j]);
			else
				printf("∞\t");
		printf("\n");
	}

	printf("Path矩陣:\n");
	for (int i = 0; i < G.vexnum; i++) {
		for (int j = 0; j < G.vexnum; j++)
			printf("%c\t", Path[i][j]);
		printf("\n");
	}

	printf("\n弗洛伊德(Floyd)最短路徑長度與最短路徑如下所示:\n");
	/***************************************************************************************
	**			這裡輸出除自己到自己的所有路徑
	**	temp:通過Path[i][j] ---找到--> Path[Path[i][j]][j]<---判斷是否相等-->j
	****************************************************************************************/
	for (int i = 0; i < G.vexnum; i++)
	{
		int temp = 0;
		for (int j = 0; j < G.vexnum; j++) {
			if(DFloyd[i][j] != INT__MAX)
			{
				if (j != i)															//這裡不需要輸出自己到自己的路徑
					printf("從頂點 %c ---> %c 最短路徑長度為:%d\t最短路徑為: %c %c", G.vexs[i], G.vexs[j], DFloyd[i][j], G.vexs[i], Path[i][j]);	//不防把起點(G.vexs[i]),和Path[i][j]先輸出(Path[i][j]是路徑的第一個點)
			}else if (DFloyd[i][j] == INT__MAX)
			{
				printf("從頂點 %c ---> %c 不可到達!!!", G.vexs[i], G.vexs[j]);	//不存在最短路徑
			}

			temp = LocateVex(G, Path[i][j]);
			while (temp != j) {														//迴圈輸出直到(Path[temp][j] == j)為止!!!
				printf(" %c", Path[temp][j]);										//輸出最短路徑
				temp = LocateVex(G, Path[temp][j]);
			}
			if (j != i)																//自己到自己不需要換行
				printf("\n");														
		}	
		printf("\n");																//到某個點路徑都顯示了,換行
	}

	free(DFloyd);																	//釋放DFloyd[][]空間
	free(Path);																		//釋放Path[][]空間
}

####程式輸出
圖5及輸出:
這裡寫圖片描述

圖6及輸出:
這裡寫圖片描述

####主函式

#include "stdafx.h"
#include "dijkatra.h"

int main()
{
	MGraph G;																			//有向圖的鄰接矩陣
	shortestCode code;																	//編碼--輸出路徑

	printf("Please enter vexnum and arcnum: ");
	scanf("%d %d", &G.vexnum, &G.arcnum);												//輸入結點數,弧數

	CreatMGraph(G);																		//建立無向圖的鄰接矩陣 
	printf("\nTne output of Adjacency Matrix:\n\n");
	printMatrixGraph(G);																//輸出鄰接矩陣

	ShortestPath_DIJ(G, G.vexs[0], code);												//迪傑斯特拉(Dijkstra)演算法--求得最短路徑長度與最短路徑
	ShortestPath_FLOYD(G, code);														//弗洛伊德(Floyd)演算法--求得最短路徑長度與最短路徑
	return 0;
}

####四:感謝與原始碼(VS2017)
1:感謝一下波主文章對我的幫助,我相信他們的文章對你也會有幫助!!!
https://blog.csdn.net/qq_34374664/article/details/52261672
https://blog.csdn.net/txl199106/article/details/44980923
2:原始碼(VS2017)
連結: https://pan.baidu.com/s/15kSoQhm2CTFHPN32OMez6g 提取碼: w8va
3:如果你覺得本文還不錯,請務必說明出處。

相關文章