介绍:
在网图和非网图中,最短路径的含义是不同的。由于非网图它没有边上的权值,所谓的最短路径,其实就是指两顶点之间经过的边数最少的路径;而对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。显然,我们研究网图更有实际意义,就地图来说,距离就是两顶点间的权值之和。而非网图完全可以理解为所有的边的权值都为1的网。
迪杰斯特拉(Dijkstra)算法
#define MAXVEX 9
#define INFINITY 65535
typedef int Pathmatirx[MAXVEX]; /* 用于存储最短路径下标的数组 */
typedef int ShortPathTable[MAXVEX]; /* 用于存储到各点最短路径的权值和 */
/* Dijkstra算法,求有向网G的v0顶点到其余顶点v最短路径P[v]及带权长度D[v] */
/* P[v]的值为前驱顶点下标,D[v]表示v0到v的最短路径长度和。 */
void ShortestPath_Dijkstra(MGraph G, int v0, Pathmatirx *P, ShortPathTable *D)
{
int v, w, k, min;
int final[MAXVEX]; /* final[w]=1表示求得顶点v0至vw的最短路径 */
for (v=0; v<G.numVertexes; v++) /* 初始化数据 */
{
final[v] = 0; /* 全部顶点初始化为未知最短路径状态 */
(*D)[v] = G.matirx[v0][v]; /* 将与v0点有连线的顶点加上权值 */
(*P)[v] = 0; /* 初始化路径数组P为0 */
}
(*D)[v0] = 0; /* v0至v0路径为0 */
final[v0] = 1; /* v0至v0不需要求路径 */
/* 开始主循环,每次求得v0到某个v顶点的最短路径 */
for (v=1; v<G.numVertexes; v++)
{
min = INFINITY; /* 当前所知离v0顶点的最近距离 */
for (w=0; w<G.numVertexes; w++) /* 寻找离v0最近的顶点 */
{
if (!final[w] && (*D)[w]<min)
{
k = w;
min = (*D)[w]; /* w顶点离v0顶点更近 */
}
}
final[k] = 1; /* 将目前找到的最近的顶点置为1 */
for (w=0; w<G.numVertexes; w++) /* 修正当前最短路径及距离 */
{
/* 如果经过v顶点的路径比现在这条路径的长度短的话 */
if (!final[w] &&(min+G.matirx[k][w]<(*D)[w]))
{
/* 说明找到了更短的路径,修改D[w]和P[w] */
(*D)[w] = min + G.matirx[k][w]; /* 修改当前路径长度 */
(*P)[w] = k;
}
}
}
}
- 算法用邻接矩阵来存储图的相关数据,final数组用来标记哪些点已经加入最短路径中,D数组用来储存更新某个顶点到其它顶点的路径长度,算法每循环一次就找到一个顶点加入最短路径中,然后判断此顶点是否能缩短原先的距离,即经过一个中间结点后更新原先的路径长度,经过二个中间结点更新路径长度,经过三个、四个……直到最后所有顶点都在最短路径中,算法结束。
- P数组用来储存最短路径的路线,即它们是如何连接的,源点要到达终点这条路怎么走,先到哪个点再到哪个点。
- 此算法解决了从某个源点到其余各顶点的最短路径问题。时间复杂度为O(n2)。
- 如果我们需要知道任一顶点到其余所有顶点的最短路径怎么办呢?简单的办法就是对每个顶点当作源点运行一次Dijkstra算法,等于在原有算法的基础上,再来一次循环,此时整个算法的时间复杂度为O(n3)。
弗洛伊德(Floyd)算法
typedef int Pathmatirx[MAXVEX][MAXVEX];
typedef int ShortPathTable[MAXVEX][MAXVEX];
/* Floyd算法,求网图G中各顶点v到其余顶点w最短路径P[v][w]及带权长度D[v][w] */
void ShortestPath_Floyd(MGraph G, Pathmatirx *P, ShortPathTable *D)
{
int v, w, k;
for (v=0; v<G.numVertexes; ++v) /* 初始化D与P */
{
for (w=0; w<G.numVertexes; ++w)
{
(*D)[v][w] = G.matirx[v][w]; /* D[v][w]值即为对应点间的权值 */
(*P)[v][w] = w; /* 初始化P */
}
}
for (k=0; k<G.numVertexes; ++k)
{
for (v=0; v<G.numVertexes; ++v)
{
for (w=0; w<G.numVertexes; ++w)
{
if ((*D)[v][w] > (*D)[v][k]+(*D)[k][w])
{
/* 如果经过下标为k顶点路径比原两点间路径更短 */
/* 将当前两点间权值设为更小的一个 */
(*D)[v][w] = (*D)[v][k] + (*D)[k][w];
(*P)[v][w] = (*P)[v][k]; /* 路径设置经过下标为k的顶点 */
}
}
}
}
/* 最短路径的显示代码 */
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存储的是顶点到顶点之间的权值,数组P存储的是顶点到顶点之间的路径。
- k代表中转顶点的下标,v代表起始顶点,w代表结束顶点。
- 循环每个顶点作为中间顶点,判断是否比原来的路径长度短,是则更新路径长度。
- 它的代码简洁到就是一个二重循环初始化加一个三重循环权值修正,就完成了所有顶点到所有顶点的最短路径计算。如此简单的实现,真是巧妙之极。
- 它的时间复杂度为O(n3)。
- 如果你面临需要求所有顶点至所有顶点的最短路径问题时,弗洛伊德(Floyd)算法是不错的选择。
总结
我们虽然对求最短路径的两个算法举例都是无向图,但它们对有向图依然有效,因为二者的差异仅仅是邻接矩阵是否对称而已。