这是一个按路径长度递增的次序产生最短路径的算法。它的思路大体是这样的。
核心思想是通过维护一个集合S,其中包含已经求出最短路径的顶点,以及一个距离数组dist,记录起始顶点到每个顶点的当前最短距离。在每一轮迭代中,选择距离数组中距离最小的顶点v,将其加入集合S,并更新起始顶点到其邻接顶点的距离。具体步骤如下:
- 初始化距离数组dist,起始顶点到自身的距离为0,其他顶点的距离初始化为无穷大。
- 选择起始顶点作为当前顶点v。
- 更新v的邻接顶点的距离:遍历v的邻接顶点u,计算起始顶点到u的距离,若小于dist[u],则更新dist[u]为新的最短距离。
- 从未加入集合S的顶点中选择距离最小的顶点作为新的当前顶点v,重复步骤3。
- 重复步骤3和步骤4,直到所有顶点都被纳入集合S。
- 最终得到起始顶点到所有顶点的最短距离。
如果还是不太明白,不要紧,现在我们来看代码,从代码的模拟运行中,再次去理解它的思想。
#define MAXVEX 9
#define INFINITY 65535
typedef int
/* 用于存储最短路径下标的数组 */
Patharc[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, Patharc *P, ShortPathTable *D)
{
int v, w, k, min;
/* final[w]=1表示求得顶点v0至vw的最短路径 */
int final[MAXVEX];
/* 初始化数据 */
for (v = 0; v < G.numVertexes; v++)
{
/* 全部顶点初始化为未知最短路径状态 */
final[v] = 0;
/* 将与v0点有连线的顶点加上权值 */
(*D)[v] = G.arc[v0][v];
/* 初始化路径数组P为-1 */
(*P)[v] = -1;
}
/* v0至v0路径为0 */
(*D)[v0] = 0;
/* v0至v0不需要求路径 */
final[v0] = 1;
/* 开始主循环,每次求得v0到某个v顶点的最短路径 */
for (v = 1; v < G.numVertexes; v++)
{
/* 当前所知离v0顶点的最近距离 */
min=INFINITY;
/* 寻找离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;
/* 修正当前最短路径及距离 */
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;
}}}}
也就是说,我们通过迪杰斯特拉(Dijkstra)算法解决了从某个源点到其余各顶点的最短路径问题。从循环嵌套可以很容易得到此算法的时间复杂度为O(n2),尽管有同学觉得,可不可以只找到从源点到某一个特定终点的最短路径,其实这个问题和求源点到其他所有顶点的最短路径一样复杂,时间复杂度依然是O(n2)。
这就好比,你吃了七个包子终于算是吃饱了,就感觉很不划算,前六个包子白吃了,应该直接吃第七个包子,于是你就去寻找可以吃一个就能饱肚子的包子,能够满足你的要求最终结果只能有一个,那就是用七个包子的面粉和馅做的一个大包子。这种只关注结果而忽略过程的思想是非常不可取的。
可如果我们还需要知道如v3到v5、v1到v7这样的任一顶点到其余所有顶点的最短路径怎么办呢?此时简单的办法就是对每个顶点当作源点运行一次迪杰斯特拉(Dijkstra)算法,等于在原有算法的基础上,再来一次循环,此时整个算法的时间复杂度就成了O(n3)。
对此,我们现在再来介绍另一个求最短路径的算法——弗洛伊德(Floyd),它求所有顶点到所有顶点的时间复杂度也是O(n3),但其算法非常简洁优雅,能让人感觉到智慧的无限魅力。好了,让我们就一同来欣赏和学习它吧