挑战性题目DSCT401:全源最短路径Floyd算法的并行实现
问题描述
n个顶点的有向图采用邻接矩阵进行储存,dist
和path
分别表示邻接矩阵和路径矩阵,初始化过程伪代码在下方给出。任意输入一个顶点规模正整数n,正确实现Floyd算法的基础上返回算法运行时间。
初始化过程: dist[i*n+j]=rand()%100
,若i==j
则dist[i*n+j]=0
。
题解
初始化数据部分,复杂度 O ( n 2 ) O(n^2) O(n2),因为初始化各数据的过程独立,故可以通过并行来加快初始化过程。但由于该部分并非制约整个程序运行速度的主要因素,因此优化效果有限。
最短路求解部分,Floyd算法的第一层循环会枚举中间转移节点
k
k
k,第二层和第三层循环枚举起点
i
i
i与终点
j
j
j,通过方程path[i][j]=max(path[i][j],path[i][k]+path[k][j])
进行状态转移。其中,枚举起点终点的循环可以通过并发加速,因为他们并不依赖彼此的运算结果,只是基于上一层
k
k
k循环的结果来进行转移。与之相反,最外层的
k
k
k循环不能通过并行实现,因为每一次循环都会尝试以第
k
k
k个节点为中介点更新最短路,因此对于每一对path[i][j]
,都需要经过所有节点的更新,其结果才是正确的。但倘若通过并行实现,每个CPU执行的程序段同时进行,每次更新时信息是“不完整”的,导致结果出错。
所以,在第二层循环语句前加上#pragma omp parallel for
从而启用并行运算,可以在保证算法正确性的同时降低时间消耗。虽然算法时间复杂度仍为
O
(
n
3
)
O(n^3)
O(n3),但除以了一个常数C=CPU核心数。
需要注意的是,评测机并不包含直接编译时omp运行需要的dll文件,因此我们需要将其编译为静态库,即使用以下编译命令:
g++ -fopenmp -static xxx.cpp -o xxx.exe
代码
这种唯一的输出是自己的运行时间的**题还是头一回见……
#pragma GCC optimize(2)
#include<cstdio>
#include<stdlib.h>
#include<time.h>
using namespace std;
const int M=1e3+5;
int n,dist[M][M],start;
int main(int argc,char* argv[])
{
start=clock();
srand(19260817);
n=atoi(argv[1]);
for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)dist[i][j]=i==j?0:rand()%100;
for(int k=1;k<=n;++k)
#pragma omp parallel for
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(dist[i][j]>dist[i][k]+dist[j][k])dist[i][j]=dist[i][k]+dist[k][j];
printf("%d\n",clock()-start);
// for(int i=1;i<=n;++i,putchar(10))
// for(int j=1;j<=n;++j)printf("%d ",dist[i][j]);
return 0;
}