Boruvka's Algorithm for Minimum Spanning Tree in C++


In graph theory, finding the minimum spanning tree (MST) of a connected weighted graph is a common problem. The MST is a subset of the graph's edges that connects all the vertices while minimizing the total edge weight. One efficient algorithm to solve this problem is Boruvka's algorithm.

Syntax

struct Edge {
   int src, dest, weight;
};

// Define the structure to represent a subset for union-find
struct Subset {
   int parent, rank;
};

Algorithm

Now, let's outline the steps involved in Boruvka's algorithm for finding the minimum spanning tree −

  • Initialize the MST as an empty set.

  • Create a subset for each vertex, where each subset contains only one vertex.

  • Repeat the following steps until the MST has V-1 edges (V is the number of vertices in the graph) −

    • For each subset, find the cheapest edge that connects it to another subset.

    • Add the selected edges to the MST.

    • Perform union operation on the subsets of the selected edges.

  • Output the MST.

Approaches

In Boruvka's algorithm, there are multiple approaches to find the cheapest edge that connects each subset. Here are two common approaches −

Approach 1: Naive Approach

For each subset, iterate through all the edges and find the minimum edge that connects it to another subset.

Keep track of the selected edges and perform union operations.

Example

#include <iostream>
#include <vector>
#include <algorithm>

struct Edge {
   int src, dest, weight;
};

// Define the structure to represent a subset for union-find
struct Subset {
   int parent, rank;
};

// Function to find the subset of an element using path compression
int find(Subset subsets[], int i) {
   if (subsets[i].parent != i)
      subsets[i].parent = find(subsets, subsets[i].parent);
   return subsets[i].parent;
}

// Function to perform union of two subsets using union by rank
void unionSets(Subset subsets[], int x, int y) {
   int xroot = find(subsets, x);
   int yroot = find(subsets, y);
   if (subsets[xroot].rank < subsets[yroot].rank)
      subsets[xroot].parent = yroot;
   else if (subsets[xroot].rank > subsets[yroot].rank)
      subsets[yroot].parent = xroot;
   else {
      subsets[yroot].parent = xroot;
      subsets[xroot].rank++;
   }
}

// Function to find the minimum spanning tree using Boruvka's algorithm
void boruvkaMST(std::vector<Edge>& edges, int V) {
   std::vector<Edge> selectedEdges; // Stores the edges of the MST

   Subset* subsets = new Subset[V];
   int* cheapest = new int[V];

   // Initialize subsets and cheapest arrays
   for (int v = 0; v < V; v++) {
      subsets[v].parent = v;
      subsets[v].rank = 0;
      cheapest[v] = -1;
   }

   int numTrees = V;
   int MSTWeight = 0;

   // Keep combining components until all components are in one tree
   while (numTrees > 1) {
      for (int i = 0; i < edges.size(); i++) {
         int set1 = find(subsets, edges[i].src);
         int set2 = find(subsets, edges[i].dest);

         if (set1 != set2) {
            if (cheapest[set1] == -1 || edges[cheapest[set1]].weight > edges[i].weight)
               cheapest[set1] = i;
            if (cheapest[set2] == -1 || edges[cheapest[set2]].weight > edges[i].weight)
               cheapest[set2] = i;
         }
      }

      for (int v = 0; v < V; v++) {
         if (cheapest[v] != -1) {
            int set1 = find(subsets, edges[cheapest[v]].src);
            int set2 = find(subsets, edges[cheapest[v]].dest);

            if (set1 != set2) {
               selectedEdges.push_back(edges[cheapest[v]]);
               MSTWeight += edges[cheapest[v]].weight;
               unionSets(subsets, set1, set2);
               numTrees--;
            }

            cheapest[v] = -1;
         }
      }
   }

   // Output the MST weight and edges
   std::cout << "Minimum Spanning Tree Weight: " << MSTWeight << std::endl;
   std::cout << "Selected Edges:" << std::endl;
   for (const auto& edge : selectedEdges) {
      std::cout << edge.src << " -- " << edge.dest << " \tWeight: " << edge.weight << std::endl;
   }

   delete[] subsets;
   delete[] cheapest;
}

int main() {
   // Pre-defined input for testing purposes
   int V = 6;
   int E = 9;
   std::vector<Edge> edges = {
      {0, 1, 4},
      {0, 2, 3},
      {1, 2, 1},
      {1, 3, 2},
      {1, 4, 3},
      {2, 3, 4},
      {3, 4, 2},
      {4, 5, 1},
      {2, 5, 5}
   };

   boruvkaMST(edges, V);

   return 0;
}

Output

Minimum Spanning Tree Weight: 9
Selected Edges:
0 -- 2 	Weight: 3
1 -- 2 	Weight: 1
1 -- 3 	Weight: 2
4 -- 5 	Weight: 1
3 -- 4 	Weight: 2

Explanation

We start by defining two structures − Edge and Subset. Edge represents an edge in the graph and contains the source, destination, and weight of the edge. Subset represents a subset for the union-find data structure and contains the parent and rank information.

The find function is a helper function that uses path compression to find the subset of an element. It recursively finds the representative (parent) of the subset to which the element belongs and compresses the path to optimize future queries.

The unionSets function is another helper function that performs the union of two subsets using union by rank. It finds the representatives of the two subsets and performs the union based on the ranks to maintain balanced trees.

The boruvkaMST function takes a vector of edges and the number of vertices (V) as input. It implements Boruvka's algorithm to find the MST.

Inside the boruvkaMST function, we create a vector selectedEdges to store the edges of the MST.

We create an array of Subset structures to represent subsets and initialize them with default values.

We also create an array cheapest to keep track of the cheapest edge for each subset.

The variable numTrees is initialized with the number of vertices, and MSTWeight is initialized to 0.

The algorithm proceeds by repeatedly combining components until all components are in one tree. The main loop runs until numTrees becomes 1.

In each iteration of the main loop, we iterate through all the edges and find the minimum weighted edge for each subset. If the edge connects two different subsets, we update the cheapest array with the index of the minimum weighted edge.

Next, we iterate through all the subsets and if a minimum weighted edge exists for a subset, we add it to the selectedEdges vector, update the MSTWeight, perform the union of the subsets, and decrement the numTrees.

Finally, we output the MST weight and the selected edges.

The main function prompts the user to enter the number of vertices and edges. It then takes input for each edge (source, destination, weight) and calls the boruvkaMST function with the input.

Approach 2: Using Priority Queue

Create a priority queue to store the edges sorted by their weights.

For each subset, find the minimum-weight edge from the priority queue that connects it to another subset.

Keep track of the selected edges and perform union operations.

Example

#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;

// Edge structure representing a weighted edge in the graph
struct Edge {
   int destination;
   int weight;

   Edge(int dest, int w) : destination(dest), weight(w) {}
};

// Function to find the shortest path using Dijkstra's algorithm
vector<int> dijkstra(const vector<vector<Edge>>& graph, int source) {
   int numVertices = graph.size();
   vector<int> dist(numVertices, INT_MAX);
   vector<bool> visited(numVertices, false);

   dist[source] = 0;
   priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
   pq.push(make_pair(0, source));

   while (!pq.empty()) {
      int u = pq.top().second;
      pq.pop();

      if (visited[u]) {
         continue;
      }

      visited[u] = true;

      for (const Edge& edge : graph[u]) {
         int v = edge.destination;
         int weight = edge.weight;

         if (dist[u] + weight < dist[v]) {
            dist[v] = dist[u] + weight;
            pq.push(make_pair(dist[v], v));
         }
      }
   }

   return dist;
}

int main() {
   int numVertices = 4;
   vector<vector<Edge>> graph(numVertices);

   // Adding edges to the graph
   graph[0].push_back(Edge(1, 2));
   graph[0].push_back(Edge(2, 5));
   graph[1].push_back(Edge(2, 1));
   graph[1].push_back(Edge(3, 7));
   graph[2].push_back(Edge(3, 3));

   int source = 0;
   vector<int> shortestDistances = dijkstra(graph, source);

   cout << "Shortest distances from source vertex " << source << ":\n";
   for (int i = 0; i < numVertices; i++) {
      cout << "Vertex " << i << ": " << shortestDistances[i] << endl;
   }

   return 0;
}

Output

Shortest distances from source vertex 0:
Vertex 0: 0
Vertex 1: 2
Vertex 2: 3
Vertex 3: 6

Explanation

In this approach, we use a priority queue to optimize the process of finding the minimum weighted edge for each subset. Here's a detailed explanation of the code −

The code structure and helper functions (such as find and unionSets) remain the same as in the previous approach.

The boruvkaMST function is modified to use a priority queue to efficiently find the minimum weighted edge for each subset.

Instead of using the cheapest array, we now create a priority queue (pq) of edges. We initialize it with the edges of the graph.

The main loop runs until numTrees becomes 1, similar to the previous approach.

In each iteration, we extract the minimum weighted edge (minEdge) from the priority queue.

We then find the subsets to which the source and destination of minEdge belong using the find function.

If the subsets are different, we add minEdge to the selectedEdges vector, update the MSTWeight, perform the union of the subsets, and decrement the numTrees.

The process continues until all components are in one tree.

Finally, we output the MST weight and the selected edges.

The main function remains the same as in the previous approach, where we have pre-defined input for testing purposes.

Conclusion

Boruvka's algorithm provides an efficient solution for finding the minimum spanning tree of a weighted graph. Our team delved into exploring two separate paths when implementing this algorithm in C++: one being the traditional or "naive" method. And another utilizing a priority queue. Depending on specific requirements of the given problem at hand. Each method holds certain advantages and could be implemented accordingly. By understanding and implementing Boruvka's algorithm, you can effectively solve minimum spanning tree problems in your C++ projects.

Updated on: 25-Jul-2023

229 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements