Quiz 2

25. Minimum Spanning Trees — Prim & Kruskal

796 words
4 min read
Python Week 1: the first filter for runtime behavior
Visual companion
Python
Type and operator map

Python Week 1: the first filter for runtime behavior

View
Revision summary

What this note is really saying

Short form

# 25. Minimum Spanning Trees — Prim & Kruskal > **What problem does this solve?** You need to connect all cities in a network with the minimum total cost of cables.

25. Minimum Spanning Trees — Prim & Kruskal

What problem does this solve? You need to connect all cities in a network with the minimum total cost of cables. A minimum spanning tree (MST) connects all vertices with |V|-1 edges while minimizing total weight.

1. MST Properties

PropertyExplanation
DefinitionA subgraph that connects all vertices with minimum total edge weight
EdgesExactly
UniquenessIf all edge weights are distinct, MST is unique
Cycle propertyThe heaviest edge in any cycle is NOT in the MST
Cut propertyLightest edge crossing any cut IS in the MST

2. Prim's Algorithm — Vertex-based (Greedy)

How It Works

Start from any vertex. Repeatedly add the cheapest edge connecting the tree to a new vertex.
python
# runnable
def prim(WList):
    """Prim's MST algorithm.
    Time: O(V²) naïve, O((V+E) log V) with heap
    Returns: parent of each vertex in MST
    """
    INF = 1 + max(d for u in WList for (v, d) in WList[u])
    visited = {v: False for v in WList}
    distance = {v: INF for v in WList}
    parent = {v: -1 for v in WList}
    visited[0] = True
    for v, d in WList[0]:
        distance[v] = d
        parent[v] = 0
    for _ in range(1, len(WList)):
        # Find unvisited vertex with minimum distance
        unvisited = [v for v in WList if not visited[v]]
        if not unvisited:
            break
        nextv = min(unvisited, key=lambda v: distance[v])
        visited[nextv] = True
        for v, d in WList[nextv]:
            if not visited[v] and d < distance[v]:
                distance[v] = d
                parent[v] = nextv
    return parent
# Graph: 0-1(2), 0-3(6), 1-2(3), 1-3(8), 1-4(5), 2-4(7)
WL = {
    0: [(1, 2), (3, 6)],
    1: [(0, 2), (2, 3), (3, 8), (4, 5)],
    2: [(1, 3), (4, 7)],
    3: [(0, 6), (1, 8)],
    4: [(1, 5), (2, 7)]
}
parent = prim(WL)
print(f"Prim's MST parent: {parent}")  # {0: -1, 1: 0, 2: 1, 3: 0, 4: 1}
# Edges: (0-1:2), (0-3:6), (1-2:3), (1-4:5) → total = 16

3. Kruskal's Algorithm — Edge-based (Greedy)

How It Works

Sort all edges by weight. Add edges one by one if they don't create a cycle.
python
# runnable
def kruskal_naive(WList):
    """Kruskal's MST (naive: O(V²) for cycle check).
    Time: O(E log E) for sorting + O(V²) for cycle checks
    """
    # Collect all edges
    edges = []
    for u in WList:
        for v, d in WList[u]:
            if u < v:  # Avoid duplicates for undirected
                edges.append((d, u, v))
    edges.sort()  # Sort by weight
    # Simple component tracking
    component = {v: v for v in WList}
    mst_edges = []
    for weight, u, v in edges:
        if component[u] != component[v]:
            mst_edges.append((u, v, weight))
            old_comp = component[u]
            new_comp = component[v]
            # Merge components
            for w in WList:
                if component[w] == old_comp:
                    component[w] = new_comp
    return mst_edges
mst = kruskal_naive(WL)
print(f"Kruskal's MST: {mst}")
# [(0, 1, 2), (1, 2, 3), (1, 4, 5), (0, 3, 6)]
# Total weight = 2 + 3 + 5 + 6 = 16

4. Union-Find Optimized Kruskal

Using the union-find data structure (next section) for O(m log n) performance.
python
# runnable
class QuickUnion:
    def __init__(self, n):
        self.parent = list(range(n))
        self.size = [1] * n
    def find(self, x):
        while self.parent[x] != x:
            self.parent[x] = self.parent[self.parent[x]]  # Path compression
            x = self.parent[x]
        return x
    def union(self, a, b):
        ra, rb = self.find(a), self.find(b)
        if ra == rb:
            return
        if self.size[ra] < self.size[rb]:
            ra, rb = rb, ra  # Ensure ra has larger size
        self.parent[rb] = ra
        self.size[ra] += self.size[rb]
def kruskal_optimized(WList):
    """Kruskal with union-find for O(E log V) performance."""
    edges = []
    for u in WList:
        for v, d in WList[u]:
            if u < v:
                edges.append((d, u, v))
    edges.sort()
    uf = QuickUnion(len(WList))
    mst_edges = []
    for weight, u, v in edges:
        if uf.find(u) != uf.find(v):
            uf.union(u, v)
            mst_edges.append((u, v, weight))
    return mst_edges
print(f"Kruskal optimized: {kruskal_optimized(WL)}")

5. Comparison: Prim vs Kruskal

FeaturePrim'sKruskal's
StrategyGrow a single treeMerge multiple trees
Data structurePriority queue (heap)Union-find
Best forDense graphsSparse graphs
Time (simple)O(V²)O(E log E)
Time (heap/UF)O((V+E) log V)O(E log V)
CorrectnessCut propertyCycle property

Practice Questions

Q1. Run Prim's from vertex 0 on: 0-1(1), 0-2(4), 1-2(2), 1-3(6), 2-3(3). Show MST edges. Q2. Run Kruskal's on the same graph. Q3. If all edge weights are equal, how many different MSTs can exist?
Answers
A1. Prim: Start at 0. Add 0-1(1). Add 1-2(2) [cheaper than 0-2(4)]. Add 2-3(3) [cheaper than 1-3(6)]. MST: (0-1:1), (1-2:2), (2-3:3). Total: 6.
A2. Sorted edges: (0-1:1), (1-2:2), (2-3:3), (0-2:4), (1-3:6). Add (0-1), (1-2), (2-3) — same as Prim. (0-2) creates cycle, skip. (1-3) creates cycle, skip.
A3. if all weights equal, any spanning tree is minimum. Number of MSTs = number of spanning trees (can be exponential). Join Discord Previous24. Bellman-Ford & Floyd-WarshallNext26. Union-Find (Disjoint Set)
Document outline

Keep your place and jump directly to a heading.

Table of Contents
System Normal // Awaiting Context

Intelligence Hub

Navigate the knowledge graph to generate context. The Hub adapts dynamically to surface backlinks, related notes, and metadata insights.