图的单源最短路径问题是指求一个从指定的顶点s到其他所有顶点i之间的最短距离。因为是从一点到其他顶点的距离,所以称谓单源。本文将介绍dijkstra算法:按路径长度递增次序的最短路径算法,并给出了对该算法MPI并行化算法。
一、算法思想
该算法的思想是:引入一个辅助向量D,它的每个分量D[i]为源顶点v到其他顶点v[i]的路径长度。初始态为:如有从v到vi的路径,则D[i]为弧[v,vi]的权值;否则D[i]为无穷大。显然
D[j] = min{D[i]}
为顶点v出发到其他顶点的一条最短路径的长度,其路径为(v,vj)。
那么下一条最短路径长度如何计算呢?其实很简单,下一条最短路径长度要么是源顶点v直接到某一顶点vk的长度,即{v,vk}。要么是源顶点v经过顶点vj到某一顶点的长度,即{v,vj,vk}。
我们假设S为已经求得最短路径的顶点的集合,那么下一条最短路径(设其终点为x),要么是弧{v, vx},要么为中间只经过S中顶点而最后到达终点X的路径。
在一般情况下,下一条最短路径的长度为:
D[j] = min{D[i] | vi 属于 V-S}
其中V为图顶点的集合, D[i]为弧{v, vi}的权值,或者为D[k]和弧{vk, vi}权值之和。
二、算法描述
根据以上思想,我们得到算法描述如下:
输入:图G的邻接矩阵m[0...n-1][0...n-1],源顶点 v
输出:最短路径值向量D[0...n-1], 最短路径矩阵p[0...n-1][0...n-2].其中D[i]为源顶点v到顶点vi的最短路径长度,向量p[i]为源顶点v到顶点vi的最短路径
1)初始化D[i]
D[i] = m[v][vi] == 无穷大 ? 无穷大 : m[v][vi]
2)计算当前最短路径值
min = min{D[i]}
final[i] = 1 //标记顶点i已经取得最短路径
3)更新最短路径值及最短路径
for(i = 0; i < n; i++)
if(!final[i])
if(D[i] > min + m[vk][vi])
D[i] = min + m[vk][vi];
end if
endif
for( j = 0; j < n-1 ; j++)
if(p[vk][j] != 无穷大)
p[vi][j] = p[vk][j];
end if
end for
p[vi][j] = vi
end for
4) 输出最短路径和最短路径值
三、算法实现
这里只给出算法的核心代码, 如下:
1: void short_path_function(
2: int **matrix,
3: int vertex_num,
4: int vertex_source){
5:
6: int *short_path_value;
7: int *final;
8:
9: int *short_path_storage;
10: int **short_path;
11:
12: int min;
13: int vertex;
14:
15: int i,j,k;
16:
17: //分配空间
18: short_path_value = my_malloc(sizeof(int) * vertex_num);
19: final = my_malloc(sizeof(int) * vertex_num);
20: dynamic_allocate_matrix((void *)&short_path, (void *)&short_path_storage, vertex_num,
21: vertex_num-1, sizeof(int));
22:
23: //初始化
24: for(i = 0; i < vertex_num; i++){
25: final[i] = 0;
26: short_path_value[i] = matrix[vertex_source][i];
27:
28: //设置空路径
29: for(j = 0; j < vertex_num-1; j++)
30: short_path[i][j] = -1;
31:
32: //初始化路径
33: if(short_path_value[i] < MAX_VAULE)
34: short_path[i][0] = i;
35: }
36:
37: final[vertex_source] = 1;
38: for(i = 1; i < vertex_num; i++){
39: //找出从源顶点出发的一条最短路径长度顶点
40: min =MAX_VAULE ;
41: for(j = 0; j < vertex_num; j++)
42: if(!final[j])
43: if(short_path_value[j] < min){
44: min = short_path_value[j];
45: vertex = j;
46: }
47:
48: final[vertex] = 1;
49:
50: //跟新最短路径
51: for(j = 0; j < vertex_num; j++){
52: if(!final[j])
53: if(min + matrix[vertex][j] < short_path_value[j]){
54: //跟新最短路径长度
55: short_path_value[j] = min + matrix[vertex][j];
56:
57: //更新路径
58: k = 0;
59: while(short_path[vertex][k] != -1){
60: short_path[j][k] = short_path[vertex][k];
61: k++;
62: }
63:
64: short_path[j][k] = j;
65: }
66: }
67: }
68:
69: //打印结果
70: array_int_print(vertex_num, short_path_value);
71: print_int_matrix(short_path, vertex_num-1, vertex_num);
72: }
四、并行算法描述
我们对这个算法进行并行化分析,显然初始化向量D(对应于算法的第一步),更新最短路径值和最短路径(对应于算法的第三步)是可以并行化实现的,因为只要有了当前最短路径和最短路径值,各个顶点的最短路径的算法是相互独立的。在本并行化算法中,如何求得当前的最短路径和最短路径值成为关键。
我们假设一共用P个进程,图右n个顶点,我们让每个进程负责n/p个顶点,每个进程都有自己的向量D和最短路径p,我们如何求得当前的最短路径长度和最短路径呢?首先,我们可以计算出各个进程的当前最短路径,并将局部最短路径发往进程0,进程0对这些进程的最短路径进行比较,取得当前的全局最短路径,并将这一结果广播到所有的进程。
其算法描述如下:
我们假设总共有p个进程
输入:图G的邻接矩阵m[0...n-1][0...n-1],源顶点 v
输出:最短路径值向量D[0...n-1], 最短路径矩阵p[0...n-1][0...n-2].
1) 进程0读取邻接矩阵m和源节点v,并将m,v广播到其他所有的进程
2) 各进程并行初始化各自的局部D和P
3)求最短路径
1)各进程并行计算出各自的局部最短路径值,并将其发送0号进程
2)0号进程求出全局最短路径值和对应的进程号,并将其广播到其他所有的进程。
3)拥有全局最短路径值的进程将其对应的最短路径广播到其他进程(用于更新各个进程的最短路径)
4)各进程并行跟新各自的D和P
五、 并行算法实现
我们这里只给出算法中第三步的具体实现:
1: int get_short_path_value(
2: int *final,
3: int *vertex,
4: int **short_path,
5: int *short_path_value,
6: int *short_path_copy,
7: int *shortest,
8: int vertex_num,
9: int local_vertex_num,
10: int process_id,
11: int process_size,
12: MPI_Comm comm){
13:
14: int min;
15: int local_vertex;
16:
17: int shortest_process_id;
18:
19: MPI_Status status;
20:
21: int j;
22:
23: //取到每个进程的路径的最小值
24: min = MAX_VAULE;
25: for(j = 0; j < local_vertex_num; j++)
26: if(!final[j])
27: if(short_path_value[j] < min){
28: min = short_path_value[j];
29: local_vertex = j;
30: }
31:
32: //各进程将自己的最小路径值发往0号进程
33: if(process_id)
34: MPI_Send(&min, 1, MPI_INT, 0, DATA_MESSAGE, comm);
35: else{
36: //0号进程收集所有进程的最小值
37: shortest[0] = min;
38: for(j = 1; j < process_size; j++)
39: MPI_Recv(shortest+j, 1, MPI_INT, j, DATA_MESSAGE, comm, &status);
40:
41: //0号进程计算出全局最小值,并广播到其他各个进程
42: min = shortest[0];
43: shortest_process_id = 0;
44: for(j = 1; j < process_size; j++)
45: if(shortest[j] < min){
46: min = shortest[j];
47: shortest_process_id = j;
48: }
49: }
50: //0号进程将最小值广播到所有进程,如果最小值为无穷大,则返回
51: MPI_Bcast(&min, 1, MPI_INT, 0, comm);
52: if(min == MAX_VAULE)
53: return min;
54:
55: //0号进程将最小值所属的进程广播到所有的进程
56: MPI_Bcast(&shortest_process_id, 1, MPI_INT, 0, comm);
57:
58: //拥有最小值的进程,将其对应的标志位标1,并计算该坐标的全局坐标
59: if(process_id == shortest_process_id){
60: final[local_vertex] = 1;
61: *vertex = BLOCK_LOW(shortest_process_id, process_size, vertex_num) + local_vertex;
62:
63: //复制最短路径
64: for(j = 0; j < vertex_num -1; j++)
65: short_path_copy[j] = short_path[local_vertex][j];
66: }
67:
68: //广播最短路径
69: MPI_Bcast(short_path_copy, vertex_num-1, MPI_INT, shortest_process_id,comm);
70: MPI_Bcast(vertex, 1, MPI_INT, shortest_process_id, comm);
71:
72: return min;
73: }
六、MPI函数说明
本并行算法用到的MPI函数主要为点对点通信函数MPI_Send和MPI_Recv,全局通信函数MPI_Bcast。MPI名字起的真好,消息传递编程,我觉得MPI算法的核心就是任务的划分和任务间的通信。
下篇将介绍图的最下生成树及其并行算法。