一、bellman_ford算法原理
Bellman-ford是单源最短路径算法,以一个点为源点,找到从这个点到其他所有点的最短路径。
下面举例来说明算法的思想。
下列表格为0节点到其他点的距离。
初始时,我们还不知道0到其他节点的距离,所以d[i]都为正无穷,除了d[0]=0,因为自己到自己的距离是0.
从0-1,这条边 d[0]+2 < 正无穷,所以我们更新d[1]=2,表示0到1的距离为2
这也就是单源最短路径算法中边的松弛操作。
Bellman-ford算法就是对每一条边都进行松弛操作,所以我们需要迭代【边数】-1次。
同理,我们可以得到d【3】等于7
下一次迭代,2-3这条边 d[2]+1<d[3]==7 所以更新d[3]=6,这就表示0节点走0-1-2-3这条路径比走0-3距离更短。
所以当迭代终止的时候,就得到从0到1、2、3的最短路径。
二、特殊情况的判定
负循环
1.负自循环
1号节点就可以通过自负循环,不断减小d[1],变成负无穷,那么后续的黄色节点
也可以通过1节点,使得距离变成负无穷。
2.非负自循环
和自负循环类似,后续的3、4、5节点可通过1、3、2这个循环,使得距离不断-1。
所以负循环是我们在算法中需要考虑的问题。
三、算法实现流程
主函数:
Bellman_ford函数:
在这里插入代码片
#include <string>
#include <cassert>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <iomanip>
#include <cstring>
#include "mpi.h"
using std::string;
using std::cout;
using std::endl;
#define INF 1000000
/**
* utils is a namespace for utility functions
* including I/O (read input file and print results) and matrix dimension convert(2D->1D) function
*/
namespace utils {
int N; //number of vertices
int *mat; // the adjacency matrix
void abort_with_error_message(string msg) {
std::cerr << msg << endl;
abort();
}
//translate 2-dimension coordinate to 1-dimension
int convert_dimension_2D_1D(int x, int y, int n) {
return x * n + y;
}
int read_file(string filename) {
std::ifstream inputf(filename, std::ifstream::in);
if (!inputf.good()) {
abort_with_error_message("ERROR OCCURRED WHILE READING INPUT FILE");
}
inputf >> N;
//input matrix should be smaller than 20MB * 20MB (400MB, we don't have too much memory for multi-processors)
assert(N < (1024 * 1024 * 20));
mat = (int *) malloc(N * N * sizeof(int));
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++) {
inputf >> mat[convert_dimension_2D_1D(i, j, N)];
}
return 0;
}
int print_result(bool has_negative_cycle, int *dist) {
std::ofstream outputf("output.txt", std::ofstream::out);
if (!has_negative_cycle) {
for (int i = 0; i < N; i++) {
if (dist[i] > INF)
dist[i] = INF;
outputf << dist[i] << '\n';
}
outputf.flush();
} else {
outputf << "FOUND NEGATIVE CYCLE!" << endl;
}
outputf.close();
return 0;
}
}//namespace utils
/**
* Bellman-Ford algorithm. Find the shortest path from vertex 0 to other vertices.
* @param my_rank the rank of current process
* @param p number of processes
* @param comm the MPI communicator
* @param n input size
* @param *mat input adjacency matrix
* @param *dist distance array
* @param *has_negative_cycle a bool variable to recode if there are negative cycles
*/
void bellman_ford(int my_rank, int p, MPI_Comm comm, int n, int *mat, int *dist, bool *has_negative_cycle) {
int loc_n; // need a local copy for N
int loc_start, loc_end;
int *loc_mat; //local matrix
int *loc_dist; //local distance
///step 1: 广播 N
if (my_rank == 0) {
loc_n = n;
}
MPI_Bcast(&loc_n, 1, MPI_INT, 0, comm);
///step 2: find local task range
int ave = loc_n / p;
loc_start = ave * my_rank;
loc_end = ave * (my_rank + 1);
if (my_rank == p - 1) {
loc_end = loc_n;
}
///step 3: allocate local memory
loc_mat = (int *) malloc(loc_n * loc_n * sizeof(int));
loc_dist = (int *) malloc(loc_n * sizeof(int));
///step 4: broadcast matrix mat 广播邻接矩阵
if (my_rank == 0)
memcpy(loc_mat, mat, sizeof(int) * loc_n * loc_n);
MPI_Bcast(loc_mat, loc_n * loc_n, MPI_INT, 0, comm);
///step 5: bellman-ford algorithm
for (int i = 0; i < loc_n; i++) {
loc_dist[i] = INF;
}
loc_dist[0] = 0;
MPI_Barrier(comm);
bool loc_has_change;
int loc_iter_num = 0;
for (int iter = 0; iter < loc_n - 1; iter++) {
loc_has_change = false;
loc_iter_num++;
for (int u = loc_start; u < loc_end; u++) {
for (int v = 0; v < loc_n; v++) {
int weight = loc_mat[utils::convert_dimension_2D_1D(u, v, loc_n)];
if (weight < INF) {
if (loc_dist[u] + weight < loc_dist[v]) {
loc_dist[v] = loc_dist[u] + weight;
loc_has_change = true;
}
}
}
}
MPI_Allreduce(MPI_IN_PLACE, &loc_has_change, 1, MPI_CXX_BOOL, MPI_LOR, comm);
if (!loc_has_change)
break;
MPI_Allreduce(MPI_IN_PLACE, loc_dist, loc_n, MPI_INT, MPI_MIN, comm);
}
//do one more step
if (loc_iter_num == loc_n - 1) {
loc_has_change = false;
for (int u = loc_start; u < loc_end; u++) {
for (int v = 0; v < loc_n; v++) {
int weight = loc_mat[utils::convert_dimension_2D_1D(u, v, loc_n)];
if (weight < INF) {
if (loc_dist[u] + weight < loc_dist[v]) {
loc_dist[v] = loc_dist[u] + weight;
loc_has_change = true;
break;
}
}
}
}
MPI_Allreduce(&loc_has_change, has_negative_cycle, 1, MPI_CXX_BOOL, MPI_LOR, comm);
}
//step 6: retrieve results back
if(my_rank == 0)
memcpy(dist, loc_dist, loc_n * sizeof(int));
//step 7: remember to free memory
free(loc_mat);
free(loc_dist);
}
int main(int argc, char **argv) {
if (argc <= 1) {
utils::abort_with_error_message("INPUT FILE WAS NOT FOUND!");
}
string filename = argv[1];
int *dist;
bool has_negative_cycle = false;
//MPI initialization
MPI_Init(&argc, &argv);
MPI_Comm comm;
int p;//number of processors
int my_rank;//my global rank
comm = MPI_COMM_WORLD;
MPI_Comm_size(comm, &p);///标识有多少个进程
MPI_Comm_rank(comm, &my_rank);///标识各个进程的编号
//only rank 0 process do the I/O
if (my_rank == 0) {
assert(utils::read_file(filename) == 0);
dist = (int *) malloc(sizeof(int) * utils::N);
}
//time counter
double t1, t2;
MPI_Barrier(comm);
t1 = MPI_Wtime();
//bellman-ford algorithm
bellman_ford(my_rank, p, comm, utils::N, utils::mat, dist, &has_negative_cycle);
MPI_Barrier(comm);
//end timer
t2 = MPI_Wtime();
if (my_rank == 0) {
std::cerr.setf(std::ios::fixed);
std::cerr << std::setprecision(6) << "Time(s): " << (t2 - t1) << endl;
utils::print_result(has_negative_cycle, dist);
free(dist);
free(utils::mat);
}
MPI_Finalize();
return 0;
}
五、测试结果
进程数为1:
进程数为2:
进程数为4:
进程数为8:
进程数为16:
进程数为24:
进程数为32:
六、测试结果分析