前言
最短路徑
也是數據結構與算法中常見的問題,不同于最小生成樹,最短路徑問題則是需要找出起點到終點之間路途最短的路徑,屬于路徑規劃范疇的問題了。本篇文章將介紹兩種最短路徑的算法。
示例:
如下圖所示,找出V0到V8兩點間距離最短的路徑。
迪杰斯特拉[Dijkstra]算法
鄰接矩陣如下圖所示:
思路:
- 初始化三個數組,final數組用于記錄V0到Vw之間最短路徑是否有結果,final[w] = 1;D數組表示V0到某個頂點Vw的路徑;P數組表示各個頂點的前驅頂點的下標。
- 從V1開始,V8結束,利用k為當前頂點下標,循環遍歷,每一次循環判斷當前頂點到其余頂點最小路徑。首先判斷final數組中是否已經有結果,final[w] = 1,則跳過,final[w] = 0,則比較此時V0-->Vw之間距離最小值 與 鄰接矩陣中arc[k][w] 的和 D[w]的大小,取較小者設為D[w]的值。即V0到Vw的最短距離。(條件:
!final[0~9] && min + G.arc[k][w] < D[w])
執行過程:
初始化三個數組,默認final數組全為0,D數組為鄰接矩陣第一行數據,代表V0到連接頂點的已知距離(并非最小距離),P數組值全為0。
-
從0開始,則(D)[v0] = 0,V0到V0距離為0; final[v0] = 1,表示V0到V0沒有路徑; (P)[v0] = -1,V0到V0沒有路徑,所以前驅頂點下標為-1;
-
來到V1頂點,k = 1, min = 1, 首先判斷final[0~9]的情況,再判斷min + G.arc[k][w] < D[w]條件是否滿足,滿足則將對應P[w]位置的值設為k,即前驅頂點為V1。
-
按照以上步驟k范圍[0~8],min 為當前D[k]的值,然后進行比對,最終得到如下結果:
最終所有的頂點都經過了。此時final數組全為1,D數組中表示V0到其余頂點最短的路徑值,P數組則是表示每個頂點的前驅頂點下標。
V0--->V3:4-->2-->1-->0
最短路徑值為7
V0--->V6:3-->4-->2-->1-->0
最短路徑值為10
代碼實現
#define MAXEDGE 20
#define MAXVEX 20
#define INFINITYC 65535
typedef int Status;
typedef struct
{
int vexs[MAXVEX];
int arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;
}MGraph;
/*用于存儲最短路徑下標的數組*/
typedef int Patharc[MAXVEX];
/*用于存儲到各點最短路徑權值的和*/
typedef int ShortPathTable[MAXVEX];
/*10.1 創建鄰近矩陣*/
void CreateMGraph(MGraph *G)
{
int i, j;
G->numEdges=16;
G->numVertexes=9;
for (i = 0; i < G->numVertexes; i++)
{
G->vexs[i]=i;
}
for (i = 0; i < G->numVertexes; i++)
{
for ( j = 0; j < G->numVertexes; j++)
{
if (i==j)
G->arc[i][j]=0;
else
G->arc[i][j] = G->arc[j][i] = INFINITYC;
}
}
G->arc[0][1]=1;
G->arc[0][2]=5;
G->arc[1][2]=3;
G->arc[1][3]=7;
G->arc[1][4]=5;
G->arc[2][4]=1;
G->arc[2][5]=7;
G->arc[3][4]=2;
G->arc[3][6]=3;
G->arc[4][5]=3;
G->arc[4][6]=6;
G->arc[4][7]=9;
G->arc[5][7]=5;
G->arc[6][7]=2;
G->arc[6][8]=7;
G->arc[7][8]=4;
for(i = 0; i < G->numVertexes; i++)
{
for(j = i; j < G->numVertexes; j++)
{
G->arc[j][i] =G->arc[i][j];
}
}
}
/*10.2 求得網圖中2點間最短路徑
Dijkstra 算法
G: 網圖;
v0: V0開始的頂點;
p[v]: 前驅頂點下標;
D[v]: 表示從V0到V的最短路徑長度和;
*/
void ShortestPath_Dijkstra(MGraph G, int v0, Patharc *P, ShortPathTable *D)
{
int v,w,k,min;
k = 0;
/*final[w] = 1 表示求得頂點V0~Vw的最短路徑*/
int final[MAXVEX];
/*1.初始化數據*/
for(v=0; v<G.numVertexes; v++)
{
//全部頂點初始化為未知最短路徑狀態0
final[v] = 0;
//將與V0 點有連線的頂點最短路徑值;
(*D)[v] = G.arc[v0][v];
//初始化路徑數組p = 0;
(*P)[v] = 0;
}
//V0到V0的路徑為0
(*D)[v0] = 0;
//V0到V0 是沒有路徑的.
final[v0] = 1;
//v0到V0是沒有路徑的
(*P)[v0] = -1;
//2. 開始主循環,每次求得V0到某個頂點的最短路徑
for(v=1; v<G.numVertexes; v++)
{
//當前所知距離V0頂點最近的距離
min=INFINITYC;
/*3.尋找離V0最近的頂點*/
for(w=0; w<G.numVertexes; w++)
{
if(!final[w] && (*D)[w]<min)
{
k=w;
//w頂點距離V0頂點更近
min = (*D)[w];
}
}
//將目前找到最近的頂點置為1;
final[k] = 1;
/*4.把剛剛找到v0到v1最短路徑的基礎上,對于v1 與 其他頂點的邊進行計算,得到v0與它們的當前最短距離;*/
for(w=0; w<G.numVertexes; w++)
{
//如果經過v頂點的路徑比現在這條路徑長度短,則更新
if(!final[w] && (min + G.arc[k][w]<(*D)[w]))
{
//找到更短路徑, 則修改D[W],P[W]
//修改當前路徑的長度
(*D)[w] = min + G.arc[k][w];
(*P)[w]=k;
}
}
}
}
int main(void)
{
printf("最短路徑-Dijkstra算法\n");
int i,j,v0;
MGraph G;
Patharc P;
ShortPathTable D;
v0=0;
CreateMGraph(&G);
ShortestPath_Dijkstra(G, v0, &P, &D);
printf("最短路徑路線:\n");
for(i=1;i<G.numVertexes;++i)
{
printf("v%d -> v%d : ",v0,i);
j=i;
while(P[j]!=-1)
{
printf("%d ",P[j]);
j=P[j];
}
printf("\n");
}
printf("\n最短路徑權值和\n");
for(i=1;i<G.numVertexes;++i)
printf("v%d -> v%d : %d \n",G.vexs[0],G.vexs[i],D[i]);
printf("\n");
return 0;
}
弗洛伊德[Floyd]算法
這種算法的大致含義就是利用中間過頂點K,計算出頂點V到頂點W(W=[0~8])之間的最短路徑,利用鄰接矩陣G,比較D[v][w]
和 D[v][k] + D[k][w]
之間的大小,更新二維數組D,并且利用二維數組P記下此時的k,公式如下:
D[v][w] = min{ D[v][w], D[v][k] + D[k][w] }
利用實時更新的二維數組D和一個記錄頂點下標k的二維數組P來記錄算法中涉及的信息。
以上是D數組和P數組默認初始值。
演算過程
k = 0 , v 的范圍[0~8], w的范圍 [0~8], 意思就是以V0頂點為中轉點,比較此時D[v][w] 與 D[v][0]+ D[0][w] 的大小,發現并沒有任何變化,D[v][w] < (D[v][0]+ D[0][w])
-
k = 1, v 的范圍[0~8], w的范圍 [0~8],意思就是以V1頂點為中轉點,比較此時D[v][w] 與 D[v][0]+ D[0][w] 的大小,一圈比較下來,得到如下結果:
k=1
如上圖中的結果,相應的也需要更改P數組紅色區域值為1,如下所示:
k=1 -
k 由 2 -> 8, v 的范圍[0~8], w的范圍 [0~8],意思就是以V1頂點為中轉點,比較此時D[v][w] 與 D[v][0]+ D[0][w] 的大小,由此循環比較,最終得到的最終結果如下:
最終結果
如上圖最終結果可知,D數組第一行即V0到各個頂點的最小距離
,結果與上方第一種方法結果一樣:
那么V0到各個頂點最短距離求出來了,但是怎么知道V0到各個頂點最短距離是怎么走的,例如V0-V8 經過哪些頂點呢?
解答:
以V0~V8為例,從P數組中看出,P[0][8] = 1, 說明經過頂點V1,將1替代0,P[1][8] = 2, 說明經過頂點2,再將2替代1得到P[2][8] = 4, 則表示經過頂點4,依次類推,最終經過定頂點情況為:V0->V1->V2->V4->V3->V6->V7->V8
代碼實現
#define MAXEDGE 20
#define MAXVEX 20
#define INFINITYC 65535
typedef int Status; /* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef struct
{
int vexs[MAXVEX];
int arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;
}MGraph;
typedef int Patharc[MAXVEX][MAXVEX];
typedef int ShortPathTable[MAXVEX][MAXVEX];
/* 11.1 構成鄰近矩陣 */
void CreateMGraph(MGraph *G)
{
int i, j;
/* printf("請輸入邊數和頂點數:"); */
G->numEdges=16;
G->numVertexes=9;
for (i = 0; i < G->numVertexes; i++)/* 初始化圖 */
{
G->vexs[i]=i;
}
for (i = 0; i < G->numVertexes; i++)/* 初始化圖 */
{
for ( j = 0; j < G->numVertexes; j++)
{
if (i==j)
G->arc[i][j]=0;
else
G->arc[i][j] = G->arc[j][i] = INFINITYC;
}
}
G->arc[0][1]=1;
G->arc[0][2]=5;
G->arc[1][2]=3;
G->arc[1][3]=7;
G->arc[1][4]=5;
G->arc[2][4]=1;
G->arc[2][5]=7;
G->arc[3][4]=2;
G->arc[3][6]=3;
G->arc[4][5]=3;
G->arc[4][6]=6;
G->arc[4][7]=9;
G->arc[5][7]=5;
G->arc[6][7]=2;
G->arc[6][8]=7;
G->arc[7][8]=4;
for(i = 0; i < G->numVertexes; i++)
{
for(j = i; j < G->numVertexes; j++)
{
G->arc[j][i] =G->arc[i][j];
}
}
}
/* 11. 2
Floyd算法,求網圖G中各頂點v到其余頂點w的最短路徑P[v][w]及帶權長度D[v][w]。
Patharc 和 ShortPathTable 都是二維數組;
*/
void ShortestPath_Floyd(MGraph G, Patharc *P, ShortPathTable *D)
{
int v,w,k;
/* 1. 初始化D與P 矩陣*/
for(v=0; v<G.numVertexes; ++v)
{
for(w=0; w<G.numVertexes; ++w)
{
/* D[v][w]值即為對應點間的權值 */
(*D)[v][w]=G.arc[v][w];
/* 初始化P P[v][w] = w*/
(*P)[v][w]=w;
}
}
//2.K表示經過的中轉頂點
for(k=0; k<G.numVertexes; ++k)
{
for(v=0; v<G.numVertexes; ++v)
{
for(w=0; w<G.numVertexes; ++w)
{
/*如果經過下標為k頂點路徑比原兩點間路徑更短 */
if ((*D)[v][w]>(*D)[v][k]+(*D)[k][w])
{
/* 將當前兩點間權值設為更小的一個 */
(*D)[v][w]=(*D)[v][k]+(*D)[k][w];
/* 路徑設置為經過下標為k的頂點 */
(*P)[v][w]=(*P)[v][k];
}
}
}
}
}
int main(void)
{
printf("Hello,最短路徑弗洛伊德Floyd算法");
int v,w,k;
MGraph G;
Patharc P;
ShortPathTable D; /* 求某點到其余各點的最短路徑 */
CreateMGraph(&G);
ShortestPath_Floyd(G,&P,&D);
//打印所有可能的頂點之間的最短路徑以及路線值
printf("各頂點間最短路徑如下:\n");
for(v=0; v<G.numVertexes; ++v)
{
for(w=v+1; w<G.numVertexes; w++)
{
printf("v%d-v%d weight: %d ",v,w,D[v][w]);
//獲得第一個路徑頂點下標
k=P[v][w];
//打印源點
printf(" path: %d",v);
//如果路徑頂點下標不是終點
while(k!=w)
{
//打印路徑頂點
printf(" -> %d",k);
//獲得下一個路徑頂點下標
k=P[k][w];
}
//打印終點
printf(" -> %d\n",w);
}
printf("\n");
}
//打印最終變換后的最短路徑D數組
printf("最短路徑D數組\n");
for(v=0; v<G.numVertexes; ++v)
{
for(w=0; w<G.numVertexes; ++w)
{
printf("%d\t",D[v][w]);
}
printf("\n");
}
//打印最終變換后的最短路徑P數組
printf("最短路徑P數組\n");
for(v=0; v<G.numVertexes; ++v)
{
for(w=0; w<G.numVertexes; ++w)
{
printf("%d ",P[v][w]);
}
printf("\n");
}
return 0;
}