图的单源最短路径问题是指求一个从指定的顶点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算法的核心就是任务的划分和任务间的通信。
下篇将介绍图的最下生成树及其并行算法。
浙公网安备 33010602011771号