使用openMP并行化Boruvka

时间:2016-06-12 19:18:35

标签: c++ algorithm openmp

我已经在C ++中顺序实现了Boruvka的算法,我知道该算法的一个优点是它可以很容易地并行。我试图使用openMP这样做,但我无法弄清楚如何让它工作。我从graph.txt中读入邻接列表,并将最小生成树的输出打印到mst.txt中。这是我对boruvka的顺序代码:

#include <iostream>
#include <fstream>
#include <sstream>


using namespace std;

// initialize data structure for edges (given in adjacency list)
struct Edge {
    int v1, v2, weight; // 2 connecting verticies and a weight
};

// initialize structure for the graph
struct Graph {
   int vertex, edge;
   Edge* e; // undirected graph so edge from v1 to v2 is same as v2 to v1
};

// Creates a graph for #verticies and #edges using arrays
struct Graph* formGraph(int vertex, int edge)
{
    Graph* graph = new Graph;
    graph->vertex = vertex;
    graph->edge = edge;
    graph->e = new Edge[edge]; // again, v1-v2 = v2-v1
    return graph;
}

// initialize structure for subsets within the graph
struct Subset {
    int parent, rank; // rank will act as counter
};

// will help to find lightest edge of sets recursively
int find(struct Subset subset[], int i)
{
    if (subset[i].parent != i) {
        subset[i].parent = find(subset, subset[i].parent);
    }
 // once it is =1
 return subset[i].parent;
 }

// A function that does union of two sets
void Union(struct Subset subs[], int set1, int set2)
{
    int root1 = find(subs, set1);
    int root2 = find(subs, set2);

    //union by ranking
    if (subs[root1].rank < subs[root2].rank) { // if rank2 is higher thats parent
        subs[root1].parent = root2;
    }

    else if (subs[root1].rank > subs[root2].rank) { // if rank1 is higher thats parent
        subs[root2].parent = root1;
    }

    else // ranks are the equal so increment rank by 1
    {
        subs[root2].parent = root1;
        subs[root1].rank++;
    }
}

// the boruvka algorithm implementation
void boruvka(struct Graph* graph) {
// set data of initial graph
int vertex = graph->vertex;
int edge = graph->edge;
Edge* e = graph->e;

//initially there will always be as many subsets as there are vertices
struct Subset *subs = new Subset[vertex];

int *lightest = new int[vertex]; // array storing least weight edge

// subset for each vertex
for (int v = 0; v < vertex; v++)
{
    subs[v].parent = v; // initial parent (none)
    subs[v].rank = 0; // initial rank (no parent so always 0)
    lightest[v] = -1; // start from -1
}

int components = vertex; // iniitial trees = number of verticies
int minWeight = 0;

// must keep going until there is only one tree
while (components > 1)
{
    // lightest weight for all edges
    for (int i=0; i<edge; i++)
    {
        // gets subsets for edges that could connect
        int set1 = find(subs, e[i].v1);
        int set2 = find(subs, e[i].v2);

        // waste of time if they're already in same set so don't check
        if (set1 == set2)
            continue;

        // if different then check which one is lightest
        else
        {
            if (lightest[set1] == -1 || e[lightest[set1]].weight > e[i].weight) {
                lightest[set1] = i;
            }

            if (lightest[set2] == -1 || e[lightest[set2]].weight > e[i].weight) {
                lightest[set2] = i;
            }
        }
    }

    // making sure the wieghts are added
    for (int i=0; i<vertex; i++)
    {
        // make sure all lightest edges are included
        if (lightest[i] != -1)
        {
            int s1 = find(subs, e[lightest[i]].v1);
            int s2 = find(subs, e[lightest[i]].v2);

            if (s1 == s2)
                continue;

            minWeight += e[lightest[i]].weight;

            // Need to sort output lexicographically!?!?!?!?!!
            printf("Edge %d-%d included in MST with weight %d\n", // prints verices and weight of edge
                   e[lightest[i]].v1, e[lightest[i]].v2,
                   e[lightest[i]].weight);

            // union subsets together, decrease component number
            Union(subs, s1, s2);
            components--;
        }
        lightest[i] = -1; // in case after first iteration lightest edges fall in same subset
     }

}

printf("Weight of MST is %d\n", minWeight);
return;
}

// main function for calling boruvka
int main() {
    ifstream infile;
    char inputFileName[] = "graph.txt"; // input filename here
    infile.open(inputFileName, ios::in);

    string line;

    getline(infile, line);
    int V = atoi(line.c_str()); // set num of vertices to first line of txt

    getline(infile, line);
    int E = atoi(line.c_str()); // set num of edges to second line of txt

    // create graph for boruvka
    struct Graph* graph = formGraph(V, E);

    if (infile.is_open()) {
        string data[3]; // initialize data array
        int count = 0; // initialize counter

        while (infile.good()) { // same as while not end of file
            getline(infile, line);
            stringstream ssin(line);
            int i = 0;

        while (ssin.good() && i < 3) {
            ssin >> data[i];
            i++;
        }

            graph->e[count].v1 = atoi(data[0].c_str());
            graph->e[count].v2 = atoi(data[1].c_str());
            graph->e[count].weight = atoi(data[2].c_str());

        count++;
    }
}

freopen("mst.txt","w",stdout); // writes output into mst.txt

// call boruvka function
boruvka(graph);

infile.close(); // close the input file
return 0;
}

我的graph.txt的一个例子就是:

9
14
0 1 4
7 8 7
1 2 8
1 7 11
2 3 7
2 5 4
2 8 2
3 4 9
3 5 14
4 5 10
5 6 2
6 7 1
6 8 6
0 7 8

这个示例的输出是正确的,放在我的mst.txt中是:

Edge 0-1 included in MST with weight 4
Edge 2-8 included in MST with weight 2
Edge 2-3 included in MST with weight 7
Edge 3-4 included in MST with weight 9
Edge 5-6 included in MST with weight 2
Edge 6-7 included in MST with weight 1
Edge 1-2 included in MST with weight 8
Edge 2-5 included in MST with weight 4
Weight of MST is 37

2 个答案:

答案 0 :(得分:0)

根据该算法,在每次迭代中,森林中的每棵树将独立地添加到森林中的一个且仅一个边缘(来自不同树的边缘可以是相同的),直到添加的边缘将整个森林连接到单树。

在这里你可以看到找到每棵树的唯一边缘可以并行完成。只要您有多个树,就可以使用多个线程来加速搜索。

答案 1 :(得分:0)

如果您有兴趣,我已经使用OpenMP编写了implementation并行Boruvka算法。

我们将图形存储为边列表(edges),其中每个边(u, v)出现两次:作为uv的边。在算法的每个步骤中,边沿均以O(E log E) = O(E log V)的时间排序。

然后在P个处理器之间分割边缘。它们每个都从其本地节点计算最短边的数组。因为为所有节点分配原始内存是在恒定时间内完成的,所以我们可以简单地将其存储为数组,而避免使用哈希映射。然后,我们使用比较和交换将处理器之间的结果合并为全局最短边数组。请注意,因为我们之前对边缘列表进行了排序,所以u中的所有边缘在边缘中构成了一个连续的段。因此,cas循环中的额外迭代总数不会超过O(P),这使我们有O(E / P + P) = O(E / P)的时间来进行此步骤。

此后,我们可以使用并行DSU算法在O(V * alpha(V) / P)时间内沿添加的边合并组件。

下一步是更新顶点和边的列表,可以分别使用O(V / P)O(E / P)中的并行累加和来完成。

由于迭代的总数为O(log V),所以总的时间复杂度为O(E log^2 V / P)