一、问题
1.问题描述
给定一个带权有向图G=(V, E),其中每条边的权是非负实数。另外,给定V中的一个顶点,称为源。现在要计算从源到所有其他各顶点的最短路长度。这里路的长度是指路上各边权之和。
2.问题分析
算法描述:
- 输入的带权有向图是G=(V, E),点集V={1,2, ……,n},顶点v是源。
- c是一个二维数组,c[i][i]表示边( i ,j )的权。 当( i ,j )不在E 时,c[i][j]是一个大数。
- dist[i]表示当前从源到顶点i的最短特殊路径长度。
-
-
(1)问题最优解可以以贪心选择开始
-
只经过S中的点,计算到V-S中点的特殊最短路径,V-S中选择使得这样的特殊路径最短的一点u,加入S
-
证明:集合S初始只含v,选择v直接相连的,边权最短的一点u加入,可以得到最优解
-
分析:现在只涉及第一点u,那么只要证明了u得到的路径值是最短的
-
对于点u:不存在c[v][k]<c[v][u] (k属于S),因此dist[u]=c[v][u]
-
因此贪心选择开始可以得到最优解
(2)最优子结构性质
-
证明:v到u的最短路径为v-v1-v2-……-vi-u ,那么v-v1-v2-……-vi是v到vi的最短路径
-
设v-v1-v2-……-vi的路径长d1,假设存在v到vi的另一条路径是最短路径,长度为d2,满足d2<d1,那么dist[u]=d1+c[vi][u]<d2+c[vi][u],矛盾,假设不成立
-
问题具有最优子结构性质
(3)步步贪心选择可以得到最优解
- |S|=s
- ①当s=1时,因为最优解可以由贪心选择开始,成立
- ②假设s=k时成立,则s=k+1时:
- 按贪心规则选择V-S中一点u,且只经过S中的点到u的最短路径为d(v , u),对于所有i∈V-S,知道d(v , u) 是d(v , i)中最小的。
- 假设u到v的最短路径经过了V-S的点,且经过V-S中第一点为x,因为最优子结构性质,此前的路径长一定为d(v , x),设x到u的路径长为d'(x , u)
- dist[u]=d(v , x) + d'(x , u) < d(v , u) ,因为d'(x , u)>0,所以d(v , x) < d(v , u),与d(v , u) 是d(v , i)中最小矛盾。
- 因此s=k时成立,步步贪心可以得到最优解
二、算法设计与分析
1.基本思想
设置顶点集合S,v到S中所有点的最短路径已知,不断选择点来扩充S。初始S只含源v,对于V-S中的点k,把只经过S中的点,到k的最短路径称为特殊路径,Dijkstra每次选择V-S中特殊路径最短的点u加入S,而且设置点u的最短路径长为特殊路径长。
2.具体步骤
- 参数:c[i][i]表示边( i ,j )的权,dist[i]表示v到i的最短路径长度,n是顶点个数,v是源点编号,prev[i]表示到点i的最短路径的倒数第二点的编号(这样相当于建立了一个由i到v的链表,可以打印出路径)
- 辅助:s[i]标记 i 点是否加入S
#define MAXINT 0xfffffff
int **c;
void Djikstra(int n, int v, int dist[], int prev[]){
bool s[n+1];
//初始化集合S,和V-S中点的特殊路径长度和路径信息pev
for(int i=1; i<=n ; i++ ){
s[i]=false;
dist[i]=c[v][i];
if(dist[i]==MAXINT)
prev[i]=0;
else
prev[i]=v;
}
dist[v]=0; s[v]=true;
//Dijkstra计算,将其余n-1个点加入S
int temp=MAXINT,u;
for(int i=1;i<n;i++){
//选出v经过S中点,到V-S中特殊路径长最小的点u,加入S
temp=MAXINT;u=v;
for(int j=1; j<=n ;j++){
if(!s[j]&&dist[j]<temp){
temp=dist[j];
u=j;
}
}
s[u]=true;
//修改V-S中点的特殊路径长度
for(int j=1; j<=n; j++){
if(!s[j]){
if(c[u][j]+dist[u]<dist[j]){
dist[j]=c[u][j]+dist[u];
prev[j]=u;
}
}
}
}
}
3.时空分析
①时间复杂性:初始化O(n),主体部分外循环n-1次,每次内循环执行n次,O(n^2),因此是O(n^2)
②空间复杂度:二维数组储存边权O(n^2),prev和dist数组储存每个点的路径信息O(n),因此O(n^2)