对于给定的有向图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]);
}
运行结果:
有向图:
运行过程图示: