jiahaipeng

我要飞得更高
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

MPI并行编程系列之五:图的单源最短路径算法

Posted on 2010-04-19 09:15  飞得更高  阅读(4069)  评论(3编辑  收藏  举报

    图的单源最短路径问题是指求一个从指定的顶点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算法的核心就是任务的划分和任务间的通信。

  下篇将介绍图的最下生成树及其并行算法。