Prim’s Algorithm Explained: Efficient MST Solutions

You want to learn about Prim’s Algorithm— a greedy algorithm used to find the Minimum Spanning Tree (MST) of a connected, undirected, weighted graph, right? The MST is a subset of edges that connects all the vertices together without any cycles and with the minimum possible total edge weight. Prim’s Algorithm builds the MST incrementally, starting from a single vertex and adding the shortest edge that connects the current MST to a vertex not yet in the MST. It’s widely used for network design (e.g., laying cables, building roads) and is efficient for dense graphs (when implemented with a binary heap) or very dense graphs (with an adjacency matrix and linear search).

I. Core Principles of Prim’s Algorithm

Prim’s Algorithm follows a greedy strategy to construct the MST step by step:

  1. Initialize: Select an arbitrary starting vertex (e.g., vertex 0) and add it to the MST set. Initialize a key array where key[v] is the weight of the shortest edge connecting vertex v to the MST set (set to infinity for all vertices except the start vertex, which is 0). Also, maintain a parent array to track the parent of each vertex in the MST (for path reconstruction).
  2. Iterate to build MST: Repeat the following steps until all vertices are included in the MST:
    • Select the minimum key vertex: Pick a vertex u that is not in the MST set and has the smallest key value (this is the greedy choice).
    • Add to MST: Include u in the MST set.
    • Update key values: For each neighbor v of u that is not in the MST set, update key[v] to be the minimum of its current value and the weight of the edge u-v. Also, set parent[v] = u if the edge u-v provides a shorter connection to the MST.
  3. Terminate: Once all vertices are in the MST, the parent array defines the edges of the MST, and the sum of the key values gives the total weight of the MST.

Key Notes

  • Greedy Choice Property: At each step, choosing the smallest edge connecting the MST to the non-MST vertices ensures the MST is built optimally (this is proven by the cut property of MSTs).
  • Graph Requirements: The graph must be connected (otherwise, no MST exists) and undirected (Prim’s can be adapted for directed graphs but is rarely used for them).
  • Handling Duplicate Edges: The algorithm automatically ignores redundant edges since it only keeps the minimum weight edge to each non-MST vertex.

II. Complete Implementation Code (Python)

Below are two implementations of Prim’s Algorithm:

  1. Naive Version: Uses linear search to find the minimum key vertex (simple, O(V²) time—good for dense graphs).
  2. Efficient Version: Uses a min-heap (priority queue) to find the minimum key vertex (O((V + E) log V) time—better for sparse graphs).

Both implementations include MST edge reconstruction and total weight calculation.

python

运行

import heapq

INF = float('inf')

# -------------------------- Naive Prim's Algorithm (O(V²)) --------------------------
def prim_naive(graph):
    """
    Naive Prim's Algorithm for MST (adjacency matrix representation)
    :param graph: V x V adjacency matrix (graph[i][j] = weight of edge i-j, INF if no edge)
    :return: (mst_edges, total_weight) - list of MST edges (u, v, weight) and total MST weight
    """
    V = len(graph)
    # key: minimum weight edge connecting vertex to MST
    key = [INF] * V
    # parent: parent of each vertex in MST (for edge reconstruction)
    parent = [-1] * V
    # mst_set: boolean array to track vertices in MST
    mst_set = [False] * V

    # Start with vertex 0 (arbitrary choice)
    key[0] = 0
    parent[0] = -1  # Root of MST has no parent

    total_weight = 0

    for _ in range(V):
        # Step 1: Find the vertex u with minimum key value not in MST
        u = -1
        min_key = INF
        for i in range(V):
            if not mst_set[i] and key[i] < min_key:
                min_key = key[i]
                u = i

        # Add u to MST and accumulate weight
        mst_set[u] = True
        total_weight += key[u]

        # Step 2: Update key values for neighbors of u
        for v in range(V):
            # Update key[v] if v not in MST and edge u-v exists and weight < key[v]
            if not mst_set[v] and graph[u][v] != INF and graph[u][v] < key[v]:
                key[v] = graph[u][v]
                parent[v] = u

    # Reconstruct MST edges
    mst_edges = []
    for v in range(1, V):  # Skip root (parent[0] = -1)
        u = parent[v]
        mst_edges.append((u, v, graph[u][v]))

    return mst_edges, total_weight

# -------------------------- Efficient Prim's Algorithm (O((V+E)logV)) --------------------------
def prim_heap(graph):
    """
    Efficient Prim's Algorithm for MST (adjacency list representation)
    :param graph: Adjacency list (dict: vertex -> list of (neighbor, weight))
    :return: (mst_edges, total_weight) - list of MST edges (u, v, weight) and total MST weight
    """
    # Get all vertices (assume vertices are numbered 0 to V-1)
    V = len(graph)
    key = {v: INF for v in graph}
    parent = {v: -1 for v in graph}
    mst_set = {v: False for v in graph}
    # Min-heap: (key_value, vertex)
    heap = []

    # Start with vertex 0
    start = 0
    key[start] = 0
    heapq.heappush(heap, (0, start))

    total_weight = 0

    while heap:
        # Step 1: Extract vertex u with minimum key value
        current_key, u = heapq.heappop(heap)

        # Skip if u is already in MST (duplicate entry in heap)
        if mst_set[u]:
            continue

        # Add u to MST and accumulate weight
        mst_set[u] = True
        total_weight += current_key

        # Step 2: Update key values for neighbors of u
        for v, weight in graph[u]:
            if not mst_set[v] and weight < key[v]:
                key[v] = weight
                parent[v] = u
                heapq.heappush(heap, (key[v], v))

    # Reconstruct MST edges
    mst_edges = []
    for v in graph:
        if parent[v] != -1:  # Skip root
            u = parent[v]
            mst_edges.append((u, v, key[v]))

    return mst_edges, total_weight

# -------------------------- Test Cases --------------------------
if __name__ == "__main__":
    # Test 1: Dense graph (adjacency matrix for naive Prim's)
    # Nodes: 0,1,2,3
    graph_matrix = [
        [0, 2, INF, 3],
        [2, 0, 1, INF],
        [INF, 1, 0, 4],
        [3, INF, 4, 0]
    ]
    mst_naive_edges, mst_naive_weight = prim_naive(graph_matrix)
    print("=== Naive Prim's (Adjacency Matrix) ===")
    print("MST Edges (u, v, weight):", mst_naive_edges)
    print("Total MST Weight:", mst_naive_weight)
    # Expected edges: [(0,1,2), (1,2,1), (0,3,3)] | Total weight: 6

    # Test 2: Sparse graph (adjacency list for heap Prim's)
    # Same graph as above (adjacency list format)
    graph_list = {
        0: [(1, 2), (3, 3)],
        1: [(0, 2), (2, 1)],
        2: [(1, 1), (3, 4)],
        3: [(0, 3), (2, 4)]
    }
    mst_heap_edges, mst_heap_weight = prim_heap(graph_list)
    print("\n=== Efficient Prim's (Adjacency List) ===")
    print("MST Edges (u, v, weight):", mst_heap_edges)
    print("Total MST Weight:", mst_heap_weight)
    # Expected same as naive version

III. Key Code Explanations

1. Naive Implementation (Adjacency Matrix)

  • Graph Representation: Uses a V x V adjacency matrix where graph[i][j] is the weight of the edge between i and j (INF if no edge). This is efficient for dense graphs (where E ≈ V²).
  • Minimum Key Search: A linear scan over all vertices finds the minimum key vertex not in the MST (O(V) per iteration, leading to O(V²) total time).
  • Key Update: For each neighbor v of u (checked via the adjacency matrix), the key value is updated if the edge u-v is shorter than the current key for v.

2. Efficient Implementation (Adjacency List + Min-Heap)

  • Graph Representation: Uses an adjacency list (dictionary) where each vertex maps to a list of (neighbor, weight) tuples—ideal for sparse graphs (where E << V²).
  • Min-Heap Usage: The heap stores (key_value, vertex) pairs and allows extracting the minimum key vertex in O(log V) time. Duplicate entries in the heap are skipped (since mst_set marks processed vertices).
  • Key Update: When a shorter edge to a non-MST vertex is found, the new key value is pushed to the heap (old, larger entries are ignored when popped).

3. MST Reconstruction

  • The parent array/ dictionary tracks the parent of each vertex in the MST. For each vertex (excluding the root), the edge (parent[v], v) is part of the MST, with weight equal to key[v].
  • The total weight of the MST is the sum of all key values (since each key[v] is the weight of the edge connecting v to the MST).

IV. Algorithm Performance Analysis

MetricNaive Prim’s (Adjacency Matrix)Efficient Prim’s (Adjacency List + Heap)Explanation
Time ComplexityO(V²)O((V + E) log V)– Naive: V iterations, each with O(V) search + O(V) update.- Heap: Each edge/vertex is processed once, with O(log V) heap operations.
Space ComplexityO(V²) (matrix) + O(V) (arrays)O(V + E) (list) + O(V) (heap/arrays)– Naive: Dominated by the adjacency matrix.- Heap: Dominated by the adjacency list (sparse graphs use less space).
Best ForDense graphs (E ≈ V²)Sparse graphs (E << V²)– Naive: Linear search is faster than heap for dense graphs (no heap overhead).- Heap: Avoids O(V²) time for sparse graphs.

Cut Property (Why Prim’s Works)

The cut property states that for any cut (partition of vertices into two sets) in a connected graph, the minimum weight edge crossing the cut is part of the MST. Prim’s Algorithm repeatedly applies this property: the MST set and non-MST set form a cut, and the minimum key vertex is the minimum edge crossing the cut.

V. Prim’s vs. Kruskal’s Algorithm (MST Algorithms)

FeaturePrim’s AlgorithmKruskal’s Algorithm
StrategyBuilds MST from a single vertex (grows a tree)Builds MST by adding edges in order of increasing weight (avoids cycles)
Data StructureAdjacency matrix (naive) / adjacency list + heap (efficient)Adjacency list + Union-Find (Disjoint Set Union, DSU)
Time ComplexityO(V²) (naive) / O((V+E)logV) (heap)O(E log E) (dominated by edge sorting)
Best ForDense graphsSparse graphs
Cycle HandlingAutomatically avoids cycles (only connects to non-MST vertices)Uses Union-Find to detect and skip cycles
Graph TypeUndirected, connectedUndirected, connected (works for disconnected graphs to find MST forest)

VI. Practical Applications

  1. Network Design: Designing minimum-cost networks (e.g., laying fiber-optic cables, electrical grids, or water pipelines) to connect multiple locations with minimal total cost.
  2. Cluster Analysis: Used in machine learning for clustering data points— the MST is used to identify clusters by removing the largest edges in the MST.
  3. Image Segmentation: In computer vision, Prim’s Algorithm helps segment images by grouping pixels with similar features (e.g., color, intensity) into regions via an MST.
  4. Road/Transportation Planning: Finding the minimum set of roads to pave to connect all cities in a region with minimal construction cost.
  5. Circuit Design: Optimizing the layout of electrical circuits by connecting components with the shortest possible wires (minimizing material cost and signal delay).

Summary

Comparison to Kruskal’s: Prim’s is better for dense graphs, while Kruskal’s excels with sparse graphs and can handle disconnected graphs (MST forests).

Prim’s Algorithm is a greedy algorithm that constructs the MST of a connected, undirected, weighted graph by incrementally adding the shortest edge connecting the current MST to non-MST vertices.

Two main implementations:

Naive (O(V²)): Uses adjacency matrix and linear search—ideal for dense graphs.

Efficient (O((V+E)logV)): Uses adjacency list and min-heap—better for sparse graphs.

Core principle: Relies on the cut property of MSTs, making the greedy choice of the minimum crossing edge optimal.

Key applications: Network design, clustering, image segmentation, and transportation planning.



了解 Ruigu Electronic 的更多信息

订阅后即可通过电子邮件收到最新文章。

Posted in

Leave a comment