對於給定的有向圖G=(V,E)及單個源點Vs,求Vs到G的其餘各頂點的最短路徑。
針對單源點的最短路徑問題,Dijkstra 提出了一種按路徑長度遞增次序產生最短路徑的算法,即迪傑斯特拉(Dijkstra)算法。
基本思想
從圖的給定源點到其它各個頂點之間客觀上應存在一條最短路徑,在這組最短路徑中,按其長度的遞增次序,依次求出到不同頂點的最短路徑和路徑長度。即按長度遞增的次序生成各頂點的最短路徑,即先求出長度最小的一條最短路徑,然後求出長度第二小的最短路徑,依此類推,直到求出長度最長的最短路徑。
算法思想說明
設給定源點爲Vs,S爲已求得最短路徑的終點集,開始時令S={Vs} 。
當求得第一條最短路徑(Vs ,Vi)後,S爲{Vs,Vi} 。根據以下結論可求下一條最短路徑。
設下一條最短路徑終點爲Vj ,則Vj只有:
- ◆ 源點到終點有直接的弧<Vs,Vj>;
- ◆ 從Vs 出發到Vj 的這條最短路徑所經過的所有中間頂點必定在S中。即只有這條最短路徑的最後一條弧纔是從S內某個頂點連接到S外的頂點Vj 。
若定義一個數組 dist[n],其每個 dist[i] 分量保存從Vs 出發中間只經過集合 S 中的頂點而到達 Vi 的所有路徑中長度最小的路徑長度值,則下一條最短路徑的終點 Vj 必定是不在 S 中且值最小的頂點,即: dist[i]=Min{ dist[k]| Vk∈V-S }
利用上述公式就可以依次找出下一條最短路徑。
算法步驟
① 令S={Vs} ,用帶權的鄰接矩陣表示有向圖,對圖中每個頂點 Vi 按以下原則置初值:
② 選擇一個頂點Vj ,使得:
, Vj就是求得的下一條最短路徑終點,將Vj 併入到S中,即S=S∪{Vj} 。
③ 對 V-S 中的每個頂點 Vk ,修改dist[k],方法是:
④ 重複②,③,直到S=V爲止。
對下圖的帶權有向圖,用 Dijkstra 算法求從頂點 0 到其餘各頂點的最短路徑,數組 dist 和 pre 的各分量的變化如下表所示。
算法分析
Dijkstra算法的主要執行是:
- ◆ 數組變量的初始化:時間複雜度是O(n) ;
- ◆ 求最短路徑的二重循環:時間複雜度是O(n2) ;
因此,整個算法的時間複雜度是O(n2) 。
算法實現
用帶權的鄰接矩陣表示有向圖, 對Prim算法略加改動就成了Dijkstra算法,將Prim算法中求每個頂點 Vk 的 lowcost 值用 dist[k] 代替即可。
◆ 設數組 pre[n] 保存從 Vs 到其它頂點的最短路徑。若 pre[i]=k,表示從 Vs 到 Vi 的最短路徑中,Vi 的前一個頂點是 Vk,即最短路徑序列是(Vs , …, Vk , Vi) 。
◆ 設數組 final[n],標識一個頂點是否已加入S中。
typedef int Status; /* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef int Boolean; /* Boolean是布爾類型,其值是TRUE或FALSE */
#include<malloc.h> /* malloc()等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<process.h> /* exit() */
#include<limits.h> //常量INT_MAX和INT_MIN分別表示最大、最小整數
/* 函數結果狀態代碼 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
#define MAX_NAME 5 /* 頂點字符串的最大長度+1 */
#define MAX_INFO 20 /* 相關信息字符串的最大長度+1 */
typedef int VRType;
typedef char InfoType;
typedef char VertexType[MAX_NAME];
/* --------------------------------- 圖的數組(鄰接矩陣)存儲表示 --------------------------------*/
#define INFINITY INT_MAX /* 用整型最大值代替∞ */
#define MAX_VERTEX_NUM 20 /* 最大頂點個數 */
typedef enum { DG, DN, AG, AN }GraphKind; /* {有向圖,有向網,無向圖,無向網} */
typedef struct
{
VRType adj; /* 頂點關係類型。對無權圖,用1(是)或0(否)表示相鄰否; */
/* 對帶權圖,c則爲權值類型 */
InfoType *info; /* 該弧相關信息的指針(可無) */
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct
{
VertexType vexs[MAX_VERTEX_NUM]; /* 頂點向量 */
AdjMatrix arcs; /* 鄰接矩陣 */
int vexnum, arcnum; /* 圖的當前頂點數和弧數 */
GraphKind kind; /* 圖的種類標誌 */
}MGraph;
/* ---------------------------------------------------------------------------------------------*/
typedef int PathMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef int ShortPathTable[MAX_VERTEX_NUM];
/* --------------------------- 需要用的圖的數組(鄰接矩陣)存儲的基本操作 --------------------------*/
int LocateVex(MGraph G, VertexType u)
{ /* 初始條件:圖G存在,u和G中頂點有相同特徵 */
/* 操作結果:若G中存在頂點u,則返回該頂點在圖中位置;否則返回-1 */
int i;
for (i = 0; i < G.vexnum; ++i)
if (strcmp(u, G.vexs[i]) == 0)
return i;
return -1;
}
Status CreateDN(MGraph *G)
{ /* 採用數組(鄰接矩陣)表示法,構造有向網G */
int i, j, k, w, IncInfo;
char s[MAX_INFO], *info;
VertexType va, vb;
printf("請輸入有向網G的頂點數,弧數,弧是否含其它信息(是:1,否:0): ");
scanf("%d,%d,%d", &(*G).vexnum, &(*G).arcnum, &IncInfo);
printf("請輸入%d個頂點的值(<%d個字符):\n", (*G).vexnum, MAX_NAME);
for (i = 0; i < (*G).vexnum; ++i) /* 構造頂點向量 */
scanf("%s", (*G).vexs[i]);
for (i = 0; i < (*G).vexnum; ++i) /* 初始化鄰接矩陣 */
for (j = 0; j < (*G).vexnum; ++j)
{
(*G).arcs[i][j].adj = INFINITY; /* 網 */
(*G).arcs[i][j].info = NULL;
}
printf("請輸入%d條弧的弧尾 弧頭 權值(以空格作爲間隔): \n", (*G).arcnum);
for (k = 0; k < (*G).arcnum; ++k)
{
scanf("%s%s%d%*c", va, vb, &w); /* %*c吃掉回車符 */
i = LocateVex(*G, va);
j = LocateVex(*G, vb);
(*G).arcs[i][j].adj = w; /* 有向網 */
if (IncInfo)
{
printf("請輸入該弧的相關信息(<%d個字符): ", MAX_INFO);
gets(s);
w = strlen(s);
if (w)
{
info = (char*)malloc((w + 1) * sizeof(char));
strcpy(info, s);
(*G).arcs[i][j].info = info; /* 有向 */
}
}
}
(*G).kind = DN;
return OK;
}
/* --------------------------------------------------------------------------------------------------*/
/* 實現算法7.15的程序。迪傑斯特拉算法的實現 */
void ShortestPath_DIJ(MGraph G, int v0, PathMatrix *P, ShortPathTable *D)
{ /* 用Dijkstra算法求有向網G的v0頂點到其餘頂點v的最短路徑P[v]及帶權長度 */
/* D[v]。若P[v][w]爲TRUE,則w是從v0到v當前求得最短路徑上的頂點。 */
/* final[v]爲TRUE當且僅當v∈S,即已經求得從v0到v的最短路徑 算法7.15 */
int v, w, i, j, min;
Status final[MAX_VERTEX_NUM];
for (v = 0; v < G.vexnum; ++v)
{
final[v] = FALSE;
(*D)[v] = G.arcs[v0][v].adj;
for (w = 0; w < G.vexnum; ++w)
(*P)[v][w] = FALSE; /* 設空路徑 */
if ((*D)[v] < INFINITY)
{
(*P)[v][v0] = TRUE;
(*P)[v][v] = TRUE;
}
}
(*D)[v0] = 0;
final[v0] = TRUE; /* 初始化,v0頂點屬於S集 */
for (i = 1; i < G.vexnum; ++i) /* 其餘G.vexnum-1個頂點 */
{ /* 開始主循環,每次求得v0到某個v頂點的最短路徑,並加v到S集 */
min = INFINITY; /* 當前所知離v0頂點的最近距離 */
for (w = 0; w < G.vexnum; ++w)
if (!final[w]) /* w頂點在V-S中 */
if ((*D)[w] < min)
{
v = w;
min = (*D)[w];
} /* w頂點離v0頂點更近 */
final[v] = TRUE; /* 離v0頂點最近的v加入S集 */
for (w = 0; w < G.vexnum; ++w) /* 更新當前最短路徑及距離 */
{
if (!final[w] && min < INFINITY&&G.arcs[v][w].adj < INFINITY && (min + G.arcs[v][w].adj < (*D)[w]))
{ /* 修改D[w]和P[w],w∈V-S */
(*D)[w] = min + G.arcs[v][w].adj;
for (j = 0; j < G.vexnum; ++j)
(*P)[w][j] = (*P)[v][j];
(*P)[w][w] = TRUE;
}
}
}
}
void main()
{
int i, j, v0 = 0; /* v0爲源點 */
MGraph g;
PathMatrix p;
ShortPathTable d;
CreateDN(&g);
ShortestPath_DIJ(g, v0, &p, &d);
printf("最短路徑數組p[i][j]如下:\n");
for (i = 0; i < g.vexnum; ++i)
{
for (j = 0; j < g.vexnum; ++j)
printf("%2d", p[i][j]);
printf("\n");
}
printf("%s到各頂點的最短路徑長度爲:\n", g.vexs[0]);
for (i = 1; i < g.vexnum; ++i)
printf("%s-%s:%d\n", g.vexs[0], g.vexs[i], d[i]);
}
運行結果:
有向圖:
運行過程圖示: