Minimum Spanning Tree (MST) Problem

Find the minimum spanning tree of a connected graph, ensuring that all vertices are connected with the minimum total edge weight.

Theory

A Minimum Spanning Tree (MST) connects all vertices of a graph with the smallest possible total edge weight, without creating any cycles. Two common algorithms for solving the MST problem are Kruskalโ€™s Algorithm and Primโ€™s Algorithm.

Kruskalโ€™s Algorithm

A greedy approach that processes edges in ascending order of weights and adds them to the MST if they do not form a cycle.

Primโ€™s Algorithm

Another greedy approach that grows the MST by starting from any arbitrary node and adding the smallest edge that connects a new vertex to the current tree.

Implementation

Kruskalโ€™s Algorithm

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n
 
    def find(self, u):
        if self.parent[u] != u:
            self.parent[u] = self.find(self.parent[u])  # Path compression
        return self.parent[u]
 
    def union(self, u, v):
        root_u = self.find(u)
        root_v = self.find(v)
        if root_u != root_v:
            if self.rank[root_u] > self.rank[root_v]:
                self.parent[root_v] = root_u
            elif self.rank[root_u] < self.rank[root_v]:
                self.parent[root_u] = root_v
            else:
                self.parent[root_v] = root_u
                self.rank[root_u] += 1
 
 
def kruskal(graph, num_vertices):
    """
    Finds the minimum spanning tree using Kruskal's algorithm.
 
    Parameters:
        graph (list): List of edges [(u, v, weight)].
        num_vertices (int): Number of vertices in the graph.
 
    Returns:
        list: Edges in the minimum spanning tree.
        int: Total weight of the MST.
    """
    uf = UnionFind(num_vertices)
    mst = []
    total_weight = 0
 
    # Sort edges by weight
    graph.sort(key=lambda x: x[2])
 
    for u, v, weight in graph:
        if uf.find(u) != uf.find(v):  # No cycle
            uf.union(u, v)
            mst.append((u, v, weight))
            total_weight += weight
 
    return mst, total_weight

Runtime:

  • Sorting Edges:
  • Union-Find Operations: , where is the inverse Ackermann function. Find takes the height of the tree, which we can prove to be up to , where is the number of vertices .
  • Overall:

Primโ€™s Algorithm

import heapq
 
def prim(graph, num_vertices):
    """
    Finds the minimum spanning tree using Prim's algorithm.
 
    Parameters:
        graph (dict): Adjacency list representation {node: [(neighbor, weight)]}.
        num_vertices (int): Number of vertices in the graph.
 
    Returns:
        list: Edges in the minimum spanning tree.
        int: Total weight of the MST.
    """
    visited = [False] * num_vertices
    mst = []
    total_weight = 0
    pq = [(0, 0, -1)]  # (weight, current_node, parent_node)
 
    while pq and len(mst) < num_vertices - 1:
        weight, current_node, parent = heapq.heappop(pq)
        if visited[current_node]:
            continue
 
        visited[current_node] = True
        if parent != -1:
            mst.append((parent, current_node, weight))
            total_weight += weight
 
        for neighbor, edge_weight in graph[current_node]:
            if not visited[neighbor]:
                heapq.heappush(pq, (edge_weight, neighbor, current_node))
 
    return mst, total_weight

Runtime:

  • Priority Queue Operations:
  • Overall:

Notes

AspectKruskalโ€™sPrimโ€™s
ApproachGreedy by edgeGreedy by growing the tree
Data StructureUnion-FindPriority Queue
Best forSparse graphsDense graphs
Runtime