您的位置:首页 > 其它

单源点最短路径

2011-12-15 12:27 190 查看
单源点最短路径

对于给定的有向图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
,其每个dist[i]分量保存从Vs 出发中间只经过集合S中的顶点而到达Vi的所有路径中长度最小的路径长度值,则下一条最短路径的终点Vj必定是不在S中且值最小的顶点,即:

dist[i]=Min{ dist[k]| Vk∈V-S }

利用上述公式就可以依次找出下一条最短路径。

算法步骤

① 令S={Vs} ,用带权的邻接矩阵表示有向图,对图中每个顶点Vi按以下原则置初值:

② 选择一个顶点Vj ,使得:
dist[j]=Min{ dist[k]| Vk∈V-S }
Vj就是求得的下一条最短路径终点,将Vj 并入到S中,即S=S∪{Vj} 。
③ 对V-S中的每个顶点Vk ,修改dist[k],方法是:
若dist[j]+Wjk<dist[k],则修改为:
dist[k]=dist[j]+Wjk ("Vk∈V-S )
④ 重复②,③,直到S=V为止。

算法实现

用带权的邻接矩阵表示有向图, 对Prim算法略加改动就成了Dijkstra算法,将Prim算法中求每个顶点Vk的lowcost值用dist[k]代替即可。

◆ 设数组pre
保存从Vs到其它顶点的最短路径。若pre[i]=k,表示从Vs 到Vi的最短路径中,Vi的前一个顶点是Vk,即最短路径序列是(Vs , …, Vk , Vi) 。

◆ 设数组final
,标识一个顶点是否已加入S中。

算法实现的关键

待求点的最短路径长度本身就是待求的,又如何找出其中的最短呢?

BOOLEAN final[MAX_VEX] ;

int pre[MAX_VEX] , dist[MAX_VEX] ;

void Dijkstra_path (AdjGraph *G, int v)

/* 从图G中的顶点v出发到其余各顶点的最短路径 */

{

int j, k, m, min ;

for ( j=0; j<G->vexnum; j++)

{

pre[j]=v ;

final[j]=FALSE ;

dist[j]=G->adj[v][j] ;

} /* 各数组的初始化 */

dist[v]=0 ;

final[v]=TRUE ; /* 设置S={v} */

for ( j=0; j<G->vexnum-1; j++) /* 其余n-1个顶点 */

{

m=0 ;

while (final[m]) m++; /* 找不在S中的顶点vk */

min=INFINITY ;

for ( k=0; k<G->vexnum; k++)

{

if (!final[k]&&dist[m]<min)

{

min=dist[k] ;

m=k ;

}

} /* 求出当前最小的dist[k]值 */

final[m]=TRUE ; /* 将第k个顶点并入S中 */

for ( j=0; j<G->vexnum; j++)

{

if (!final[j]&&(dist[m]+G->adj[m][j]<dist[j]))

{

dist[j]=dist[m]+G->adj[m][j] ;

pre[j]=m ;

}

} /* 修改dist和pre数组的值 */

} /* 找到最短路径 */

}

算法分析

Dijkstra算法的主要执行是:

◆ 数组变量的初始化:时间复杂度是O(n) ;

◆ 求最短路径的二重循环:时间复杂度是O(n2) ;

因此,整个算法的时间复杂度是O(n2) 。

对图7-25的带权有向图,用Dijkstra算法求从顶点0到其余各顶点的最短路径,数组dist和pre的各分量的变化如表7-3所示。

求最短路径时数组dist和pre的各分量的变化情况

顶点
步骤
1
2
3
4
5
S
初态
Dist

pre

20

0

60

0



0

10

0

65

0

{0}

1
Dist

pre

20

0

60

0



0

10

0

30

4

{0, 4}

2
Dist

pre

20

0

50

1

90

1

10

0

30

4

{0, 4, 1}

3
Dist

pre

20

0

45

5

90

1

10

0

30

4

{0, 4, 1, 5}

4
Dist

pre

20

0

45

5

85

2

10

0

30

4

{0, 4, 1, 5, 2}

5
Dist

pre

20

0

45

5

85

2

10

0

30

4

{0, 4, 1, 5, 2, 3}

每一对顶点间的最短路径

每次以一个不同的顶点为源点重复Dijkstra算法便可求得每一对顶点间的最短路径,时间复杂度是O(n3) 。

弗罗伊德(Floyd)提出了另一个算法,其时间复杂度仍是O(n3) , 但算法形式更为简明,步骤更为简单,数据结构仍然是基于图的邻接矩阵。

算法思想

设顶点集S(初值为空),用数组A的每个元素A[i][j]保存从Vi只经过S中的顶点到达Vj的最短路径长度,其思想是:

① 初始时令S={ } , A[i][j]的赋初值方式是:

② 将图中一个顶点Vk 加入到S中,修改A[i][j]的值,修改方法是:

A[i][j]=Min{A[i][j] , (A[i][k]+A[k][j]) }

原因: 从Vj只经过S中的顶点(Vk)到达Vj的路径长度可能比原来不经过Vk的路径更短。

③ 重复②,直到G的所有顶点都加入到S中为止。

2 算法实现

◆ 定义二维数组Path

(n为图的顶点数) ,元素Path[i][j]保存从Vi到Vj的最短路径所经过的顶点。

◆ 若Path[i][j]=k:从Vi到Vj 经过Vk ,最短路径序列是(Vi , …, Vk , …, Vj) ,则路径子序列:(Vi , …, Vk)和(Vk , …, Vj)一定是从Vi到Vk和从Vk到Vj 的最短路径。从而可以根据Path[i][k]和Path[k][j]的值再找到该路径上所经过的其它顶点,…依此类推。

◆ 初始化为Path[i][j]=-1,表示从Vi到Vj 不经过任何(S中的中间)顶点。当某个顶点Vk加入到S中后使A[i][j]变小时,令Path[i][j]=k。

表7-4给出了利用Floyd算法求图7-26的带权有向图的任意一对顶点间最短路径的过程。

根据上述过程中Path[i][j]数组,得出:

V0到V1 :最短路径是{ 0, 1 } ,路径长度是2 ;

V0到V2 :最短路径是{ 0, 1, 2 } ,路径长度是6 ;

V1到V0 :最短路径是{ 1, 2, 0 } ,路径长度是9 ;

V1到V2 :最短路径是{ 1, 2 } ,路径长度是4 ;

V2到V0 :最短路径是{ 2, 0 } ,路径长度是5 ;

V2到V1 :最短路径是{ 2, 0, 1 } ,路径长度是7 ;

算法实现

int A[MAX_VEX][MAX_VEX] ;

int Path[MAX_VEX][MAX_VEX] ;

void Floyd_path (AdjGraph *G)

{

int j, k, m ;

for ( j=0; j<G->vexnum; j++)

for ( k=0; k<G->vexnum; k++)

{

A[j][k]=G->adj[j][k] ;

Path[j][k]=-1 ;

}

/* 各数组的初始化 */

for ( m=0; m<G->vexnum; m++)

for ( j=0; j<G->vexnum; j++)

for ( k=0; k<G->vexnum; k++)

if ((A[j][m]+A[m][k])<A[j][k])

{

A[j][k]=A[j][m]+A[m][k] ;

Path[j][k]=k ;

} /* 修改数组A和Path的元素值 */

for ( j=0; j<G->vexnum; j++)

for ( k=0; k<G->vexnum; k++)

if (j!=k)

{

printf(“%d到%d的最短路径为:\n”, j, k) ;

printf(“%d ”,j) ; prn_pass(j, k) ;

printf(“%d ”, k) ;

printf(“最短路径长度为: %d\n”,A[j][k]) ;

}

}/* end of Floyd */

void prn_pass(int j , int k)

{

if (Path[j][k]!=-1)

{

prn_pass(j, Path[j][k]) ;

printf(“, %d” , Path[j][k]) ;

prn_pass(Path[j][k], k) ;

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: