Floyd-Warshall算法及其并行化实现(基于MPI)

Floyd-Warshall算法(或称Floyd算法)是用于寻找加权图中非固定起止点间最短路径的经典算法,它是基于动态规划思想设计的。当前我们所认识的Floyd算法之形式由计算机科学家(同时也是图灵奖得主) Robert Floyd 于 1962 年提出并发表。但在此之前,Bernard Roy(1959)和 Stephen Warshall(1962)也分别独立地提出了类似的算法。本文将主要讨论基于MPI的并行化Floyd算法实现。

欢迎关注白马负金羁的博客 http://blog.csdn.net/baimafujinji,为保证公式、图表得以正确显示,强烈建议你从该地址上查看原版博文。本博客主要关注方向包括:数字图像处理、算法设计与分析、数据结构、机器学习、数据挖掘、统计分析方法、自然语言处理。


串行实现

鉴于Floyd算法非常有名,解释其基本原理的资料非常多,此处并不打算对Floyd算法本身做过多的赘述。如果你对Floyd算法本身还不甚了解,可以参考《算法之美——隐匿在数据结构背后的原理(C++版)》一书中的第8章第4节之介绍。

但是为了作为并行实现的对照版本,我们还是先给出一个用C++串行实现的Floyd算法。对于下面程序,我们做如下约定:

  • 图是有向的;
  • 初始化时如果两个节点之间没有边直接可达,则赋值为-1(NOT_CONNECTED);
  • 节点的标记从1开始,即第1个节点,第2个节点,等等,但没有第0个节点。

    下面给出示例代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

#define MAX 10
#define NOT_CONNECTED -1

int distance[MAX][MAX];

//number of nodes
int nodesCount;

//initialize all distances to
void Initialize(){
    memset(distance, NOT_CONNECTED , sizeof(distance));

    for (int i=0;i<MAX;++i)
        distance[i][i]=0;
}

int main(){

    Initialize();

    //get the nodes count
    scanf("%d", &nodesCount);

    //edges count
    int m;
    scanf("%d", &m);

    while(m--){
        //nodes - let the indexation begin from 1
        int a, b;

        //edge weight
        int c;

        scanf("%d-%d-%d", &a, &c, &b);
        distance[a][b]=c;
    }

    //Floyd-Warshall
    for (int k=1;k<=nodesCount;++k){
        for (int i=1;i<=nodesCount;++i){
            if (distance[i][k]!=NOT_CONNECTED){
                for (int j=1;j<=nodesCount;++j){
                    if (distance[k][j]!=NOT_CONNECTED && (distance[i][j]==NOT_CONNECTED || distance[i][k]+distance[k][j]<distance[i][j])){
                        distance[i][j]=distance[i][k]+distance[k][j];
                    }
                }
            }
        }
    }

    for (int i = 1; i <= nodesCount; ++i)
    {
        for (int j = 1; j <= nodesCount; ++j)
        {
            printf("%d ", distance[i][j]);
        }
        printf("\n");
    }

    return 0;
}

上述代码读入一个用来表示有向加权图的文件,文件中第一行表示节点数,第二行表示边数,之后每一行表示一条加权表,例如graph.txt文件内容如下

4
5
1-1-2
1-10-4
2-2-3
2-3-4
3-1-4

其中1-1-2表示从节点1到节点2之间的边的权重为1。

另外两个可以用于测试的图文件:graph2.txt

5
9
1-5-2
2-2-3
1-3-3
5-1-1
4-1-5
1-2-4
4-4-3
3-7-5
2-3-5

graph3.txt

5
7
1-13-5
1-6-4
5-2-2
1-6-2
2-3-3
4-1-3
4-5-5

执行我们的程序可得如下之结果:

$ g++-5 Floyd_s.cpp -o a.out
$ ./a.out<graph3.txt
0 6 7 6 11
-1 0 3 -1 -1
-1 -1 0 -1 -1
-1 7 1 0 5
-1 2 5 -1 0
$ ./a.out<graph2.txt
0 5 3 2 3
4 0 2 6 3
8 13 0 10 7
2 7 4 0 1
1 6 4 3 0
$ ./a.out<graph.txt
0 1 3 4
-1 0 2 3
-1 -1 0 1
-1 -1 -1 0

并行实现

现在来讨论并行实现的思路。基本想法是把一个大矩阵,按行进行划分,每个处理器(或计算节点,注意是分布式Supercomputer上的节点,不是图中的节点)分别负责矩阵中的几行,例如我们的矩阵大小是16×16,准备在四个处理器上并行计算,那么就像下图一样把整个矩阵按行分为四个小矩阵:A、B、C、D。然后每个处理分别负责其中之一。

下面是我在C++下实现的基于MPI的并行Floyd算法。

//Author: http://blog.csdn.net/baimafujinji/

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "mpi.h"

using namespace std;

#define MAX 10
#define NOT_CONNECTED -1

int distances[MAX][MAX];
int result[MAX][MAX];
//number of nodes
int nodesCount;

//initialize all distances to
void Initialize(){

    memset(distances, NOT_CONNECTED , sizeof(distances));
    memset(result, NOT_CONNECTED , sizeof(result));

    for (int i=0;i<MAX;++i)
        distances[i][i]=0;
}

int cmp ( const void *a , const void *b ){
    return *(int *)a - *(int *)b;
}

int main(int argc, char *argv[]){

    Initialize();

    //get the nodes count
    scanf("%d", &nodesCount);

    //edges count
    int m;
    scanf("%d", &m);

    while(m--){
        //nodes - let the indexation begin from 1
        int a, b;

        //edge weight
        int c;

        scanf("%d-%d-%d", &a, &c, &b);
        distances[a][b]=c;
    }

    int size, rank;

    MPI_Init(&argc,&argv);

    MPI_Datatype rtype;

    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    int slice = (nodesCount)/size;

    MPI_Bcast(distances, MAX*MAX, MPI_INT, 0, MPI_COMM_WORLD);
    MPI_Bcast(&nodesCount, 1, MPI_INT, 0, MPI_COMM_WORLD);
    MPI_Bcast(&slice, 1, MPI_INT, 0, MPI_COMM_WORLD);

    //Floyd-Warshall
    int sent=1;

    for (int k=1;k<=nodesCount;++k){

        int th = 1;
        for(; th <= size; th++)
        {
            if(1+slice*(th-1) <= k && k <= slice*th)
                sent = th;
        }
        if(1+slice*(th-1) <= k && k <= nodesCount )
            sent = size;

        MPI_Bcast(&distances[k], nodesCount+1, MPI_INT, sent-1, MPI_COMM_WORLD);

        if(rank != size-1){

            for (int i=1+slice*(rank);i<=slice*(rank+1);++i){
                if (distances[i][k]!=NOT_CONNECTED){
                    for (int j=1;j<=nodesCount;++j){
                        if (distances[k][j]!=NOT_CONNECTED && (distances[i][j]==NOT_CONNECTED
                            || distances[i][k]+distances[k][j]<distances[i][j])){
                            distances[i][j]=distances[i][k] + distances[k][j];
                        }
                    }
                }
            }
        }

        else{
            for (int i=1+slice*rank;i<=nodesCount;++i){
                if (distances[i][k]!=NOT_CONNECTED){
                    for (int j=1;j<=nodesCount;++j){
                        if (distances[k][j]!=NOT_CONNECTED && (distances[i][j]==NOT_CONNECTED
                            || distances[i][k]+distances[k][j]<distances[i][j])){
                            distances[i][j]=distances[i][k]+distances[k][j];
                        }
                    }
                }
            }
        }
    }

    for (int k=1;k<=nodesCount;++k){

        int th = 1;
        for(; th <= size; th++)
        {
            if(1+slice*(th-1) <= k && k <= slice*th)
                sent = th;
        }
        if(1+slice*(th-1) <= k && k <= nodesCount )
            sent = size;

        MPI_Bcast(&distances[k], nodesCount+1, MPI_INT, sent-1, MPI_COMM_WORLD);
    }

    MPI_Reduce(distances, result, MAX*MAX, MPI_INT, MPI_MIN, 0, MPI_COMM_WORLD);

    if(rank==0)
    {
        for(int i = 1; i <= nodesCount; i++)
        {
            for(int j = 1; j <= nodesCount; j++)
            {
                printf("%d ", result[i][j]);
            }
            printf("\n");
        }
        printf("\n");
    }

    /* Shut down MPI */
    MPI_Finalize();

    return 0;
}

完成编码后,我们来验证一下上面程序的计算结果是否和串行实现的版本所得一致。

$ mpirun -n 3 ./a.out<graph3.txt
0 6 7 6 11
-1 0 3 -1 -1
-1 -1 0 -1 -1
-1 7 1 0 5
-1 2 5 -1 0 

$ mpirun -n 3 ./a.out<graph2.txt
0 5 3 2 3
4 0 2 6 3
8 13 0 10 7
2 7 4 0 1
1 6 4 3 0 

$ mpirun -n 3 ./a.out<graph.txt
0 1 3 4
-1 0 2 3
-1 -1 0 1
-1 -1 -1 0

可见我们的并行程序输出了预期的结果。

(本文完)

时间: 2024-11-01 11:05:05

Floyd-Warshall算法及其并行化实现(基于MPI)的相关文章

Floyd最短路径算法

暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图.为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程. 上图中有4个城市8条公路,公路上的数字表示这条公路的长短.请注意这些公路是单向的.我们现在需要求任意两个城市之间的最短路程,也就是求任意两个点之间的最短路径.这个问题这也被称为“多源最短路径”问题. 现在需要一个数据结构来存储图的信息,我们仍然可以用一个4*4的矩阵(二维数组e)来存储.比如1号城市到2号城市的路程为2,则设e[1][2]

poj 3660 Cow Contest(warshall算法)

poj 3660 Cow Contest Description N (1 ≤ N ≤ 100) cows, conveniently numbered 1..N, are participating in a programming contest. As we all know, some cows code better than others. Each cow has a certain constant skill rating that is unique among the co

uva 125 Numbering Paths(warshall算法)

uva 125 Numbering Paths Description Download as PDF Background Problems that process input and generate a simple yes'' orno" answer are called decision problems. One class of decision problems, the NP-complete problems, are not amenable to general ef

Warshall算法

---用Warshall算法解决传递闭包问题 ---在一个关系R中,如果任意的(a,b)和(b,c)在关系R中,则(a,c)也一定在关系R中,则称R是可传递的.一个关系R的传递闭包是包 含R的最小的传递关系. ---Warshall算法不用来计算最短路径,而是用来判断i和j之间是否单向连接,无论是直接连接还是间接连接 ---初始条件不再是邻接矩阵,而是关系矩阵,如果两个点之间直接单向连接,那么在矩阵中对应的值就为1,反之为0 ---根据已有的直接连接关系得出其它所有的间接连接关系,即判断两点之间

【Vj作业】【拓扑排序经典理解题】Ordering Tasks 1、Kahn算法;2、基于DFS的算法。

2018-02-13 链接   https://cn.vjudge.net/contest/211129#problem/D John has n tasks to do. Unfortunately, the tasks are not independent and the execution of one task is only possible if other tasks have already been executed.InputThe input will consist o

浅谈分词算法(2)基于词典的分词方法

[TOC] 前言 在浅谈分词算法(1)分词中的基本问题中我们探讨了分词中的基本问题,也提到了基于词典的分词方法.基于词典的分词方法是一种比较传统的方式,这类分词方法有很多,如:正向最大匹配(forward maximum matching method, FMM).逆向最大匹配(backward maximum matching method,BMM).双向扫描法.逐词遍历法.N-最短路径方法以及基于词的n-gram语法模型的分词方法等等.对于这类方法,词典的整理选择在其中占到了很重要的作用,本

Warshall算法求传递闭包

传递闭包 在数学中,在集合 X 上的二元关系 R 的传递闭包是包含 R 的 X 上的最小的传递关系. 例如,如果 X 是(生或死)人的集合而 R 是关系“为父子”,则 R 的传递闭包是关系“x 是 y 的祖先”.再比如,如果 X 是空港的集合而关系 xRy 为“从空港 x 到空港 y 有直航”,则 R 的传递闭包是“可能经一次或多次航行从 x 飞到 y”. Warshall算法 Warshall在1962年提出了一个求关系的传递闭包的有效算法.其具体过程如下,设在n个元素的有限集上关系R的关系矩

求传递闭包的warshall算法

———————————————————————————— Question:R是定义于集合S上的二元关系,求R的传递闭包. Input:relation R,set A Output:t(R),which is the transitive closure of R  Solution:Warshall algorithm ———————————————————————————— 传递闭包(transitive closure) R* = R1 ∪ R2 ∪ R3 ∪ ...... ∪ Rn  

目标跟踪算法----KCF进阶(基于KCF改进的算法总结)

一.前情提要 如果你对目标跟踪和KCF是什么东西还不了解的话欢迎你看前一篇博文KCF入门详解:http://blog.csdn.net/crazyice521/article/details/53525366.如果你已经对基于KCF的目标跟踪有了一定的了解,并想知道这个算法有怎么样的后续的发展的话,就请听我慢慢介绍以下的东西. 二.KCF的弊端 说道KCF的缺点的话作者在文章中也已经算是说明了,第一点,KCF因为在跟踪过程当中目标框是已经设定好的,从始至终大小为发生变化,但是我们的跟踪序列当中目