第45课 - 最短路径
1. 最短路径
如果从有向图中某一顶点(称为源点)到达零一顶点(称为终点)的路径可能不止一条,如何找到一条路径使得沿此路径上各边上的权值总和达到最小。
问题解决:
单源最短路径问题:Dijkstra算法。
所有顶点之间的最短路径:Floyd算法。
2. 问题分析
问题的提法:给定一个带权有向D与源点v,求从v到D中其它顶点的最短路径。限定各边上的权值大于0。
解决思路:Dijkstra提出按路径长度的递增次序,逐步产生最短路径的算法。首先求出长度最短的一条最短路径,再参照它求出长度次短的一条最短路径,以此类推,直到从顶点v到其它各顶点的最短路径全部求出为止。
3. 解决步骤描述
设置辅助数组dist。它的每一个分量dist[i]表示当前找到的从源点v0到终点vi最短路径的长度。
初始状态:若从源点v0到顶点vi有边:dist[i]为该边上的权值。
若从源点v0到顶点vi无边:dist[i]为无穷。
(1) 初始化:; dist[j] Edge[0][j],j = 1,2,...,n-1;
找出最短路径所对应的点K:
(2) dist[k] == min{dist[i]},;
;
(3) 对于每一个修改:
dist[i] min{dist[i],dist[k]+Edge[k][i]}
(4) 判断:若S=V,则算法结束,否则转(2)
4. 算法精髓
S集内的顶点是已经找到最短路径的顶点。
V0到w的最短路径只能通过S集内的顶点。
D[w]可能改变:
if(D[u] + edge[u, w] <D[w])
{
D[w] = D[u] +edge[u,w];
}
5. 举例:源点为0
6. 程序--最短路径算法的实现
Dijkstra.c
#include <stdio.h>
#include <stdlib.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
#define VNUM 5
#define MV 65536
int P[VNUM];
int Dist[VNUM];
int Mark[VNUM];
int Matrix[VNUM][VNUM] =
{
{0, 10, MV, 30, 100},
{MV, 0, 50, MV, MV},
{MV, MV, 0, MV, 10},
{MV, MV, 20, 0, 60},
{MV, MV, MV, MV, 0},
};
void Dijkstra(int sv) // O(n*n)
{
int i = 0;
int j = 0;
if( (0 <= sv) && (sv < VNUM) )
{
for(i=0; i<VNUM; i++)
{
Dist[i] = Matrix[sv][i];
P[i] = sv;
Mark[i] = 0;
}
Mark[sv] = 1;
for(i=0; i<VNUM; i++)
{
int min = MV;
int index = -1;
for(j=0; j<VNUM; j++)
{
if( !Mark[j] && (Dist[j] < min) )
{
min = Dist[j];
index = j;
}
}
if( index > -1 )
{
Mark[index] = 1;
}
for(j=0; j<VNUM; j++)
{
if( !Mark[j] && (min + Matrix[index][j] < Dist[j]) )
{
Dist[j] = min + Matrix[index][j];
P[j] = index;
}
}
}
for(i=0; i<VNUM; i++)
{
int p = i;
printf("%d -> %d: %d\n", sv, p, Dist[p]);
do
{
printf("%d <- ", p);
p = P[p];
} while( p != sv );
printf("%d\n", p);
}
}
}
int main(int argc, char *argv[])
{
Dijkstra(0);
return 0;
}
7. 所有顶点之间的最短路径
问题的提法:
已知一个各边权值均大于0的带权有向图,对每一对顶点vi≠vj,要求求出vi 与vj之间的最短路径和最短路径长度。
方法:
Dijkstra:把有向图中的每一个顶点作为源点,重复执行。
8. Floyd算法基本思想
定义一个n阶方阵序列:A(-1), A(0), …, A(n-1)
其中:A(-1) [i][j] = Edge[i][j]
A(k) [i][j] = min { A(k-1)[i][j],A(k-1)[i][k] + A(k-1)[k][j] }
k = 0, 1, …, n-1
9. A矩阵的意义
A(0) [i][j]是从顶点vi到vj,中间顶点是v0的最短路径的长度。
A(k) [i][j]是从顶点vi到vj,中间顶点的序号不大于k的最短路径的长度。
A(n-1) [i][j]是从顶点vi到vj的最短路径长度。
10. Floyd算法的实现
Floyd.c
#include <stdio.h>
#include <stdlib.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
#define VNUM 5
#define MV 65536
int P[VNUM][VNUM];
int A[VNUM][VNUM];
int Matrix[VNUM][VNUM] =
{
{0, 10, MV, 30, 100},
{MV, 0, 50, MV, MV},
{MV, MV, 0, MV, 10},
{MV, MV, 20, 0, 60},
{MV, MV, MV, MV, 0},
};
void Floyd() // O(n*n*n)
{
int i = 0;
int j = 0;
int k = 0;
for(i=0; i<VNUM; i++)
{
for(j=0; j<VNUM; j++)
{
A[i][j] = Matrix[i][j];
P[i][j] = j;
}
}
for(i=0; i<VNUM; i++)
{
for(j=0; j<VNUM; j++)
{
for(k=0; k<VNUM; k++)
{
if( (A[j][i] + A[i][k]) < A[j][k] )
{
A[j][k] = A[j][i] + A[i][k];
P[j][k] = P[j][i];
}
}
}
}
for(i=0; i<VNUM; i++)
{
for(j=0; j<VNUM; j++)
{
int p = -1;
printf("%d -> %d: %d\n", i, j, A[i][j]);
printf("%d", i);
p = i;
do
{
p = P[p][j];
printf(" -> %d", p);
} while( p != j);
printf("\n");
}
}
}
int main(int argc, char *argv[])
{
Floyd();
return 0;
}
小结:
(1) Dijkstra最短路径算法是基于递推的思想设计的。
(2) 未达顶点的最短路径一定是由已达顶点的最短路径。
(3) Floyd最短路径算法只是Dijkstra最短路径算法的加强,其本质还是递推。