21. Minimum Spanning Trees
Exercises
21.1-1
Available in the latest revision of the IM.
21.1-2
Available in the latest revision of the IM.
21.1-3
Available in the latest revision of the IM.
21.1-4
Available in the latest revision of the IM.
21.1-5
Available in the latest revision of the IM.
21.1-6
Available in the latest revision of the IM.
21.1-7
Available in the latest revision of the IM.
21.1-8
Let be a connected, undirected graph with vertices. Let and be two minimum spanning trees (MSTs) of . For any real number , let be the set of all edges in that have a weight less than or equal to . Let be the subgraph of containing all vertices of but only the edges in . Let be the number of connected components in the subgraph .
Consider the edges of that have a weight of or less. Let's call this set . Because is an MST, must form a maximal cycle-free subgraph (a spanning forest) of .
Why is it maximal? If it were not maximal, there would exist some edge that we could add to without creating a cycle. Since is a spanning tree of the entire graph , adding to must create exactly one cycle, . As in the proof of Theorem 21.1 and Exercise 21.1-6,, we can conclude that every other edge in must have a weight (the well-known cycle property of MSTs). This means the entire cycle is contained within , which contradicts the assumption that adding to wouldn't create a cycle.
Any spanning forest of a graph with vertices and connected components has exactly edges. Thus, the number of edges in both and with weight is exactly . Because the number of components depends only on the original graph and the threshold (and not on the specific spanning tree chosen), both and will always have the exact same number of edges with weight . Since this holds true for any arbitrary weight , the distribution of edge weights must be identical in both trees. Consequently, their sorted lists of edge weights, , must be the same.
21.1-9
Available in the latest revision of the IM.
21.1-10
Available in the latest revision of the IM.
★ 21.1-11 🌟
Introduces an algorithm for incrementally updating an MST when an "external" edge weight decreases.
The most efficient way to solve this is by using the cycle property of minimum spanning trees (see Exercise 21.1-8). When you add any new edge to an existing spanning tree, it creates exactly one simple cycle. The cycle property states that the heaviest edge on any cycle cannot belong to the minimum spanning tree. Let the edge whose weight decreased be . The step-by-step algorithm is as follows:
Add the edge: Temporarily add the updated edge to your existing minimum spanning tree . The graph now contains exactly one cycle, .
Find the cycle: Run a DFS on the tree starting from vertex to find the path to vertex . This path, along with edge , forms the cycle .
Find the heaviest edge: Traverse the edges of the cycle and identify the edge that has the maximum weight. Update :
If , then replace with in .
Otherwise, keep the original tree .
Because is a tree with vertices, it has exactly edges. Running a DFS to find a path in a tree takes time. Finding the maximum weight edge on that path also takes time. Therefore, the entire algorithm runs in time, which is much faster than recalculating the MST from scratch.
21.2-1
Available in the latest revision of the IM.
21.2-2
Available in the latest revision of the IM.
21.2-3
Available in the latest revision of the IM.
21.2-4
Available in the latest revision of the IM.
21.2-5
Available in the latest revision of the IM.
21.2-6
Available in the latest revision of the IM.
★ 21.2-7
Available in the latest revision of the IM.
The analysis of Prim's algorithm in the IM is wrong. Because grows significantly slower than , the IM's complex bucket-based priority queue is actually a downgrade from a standard Fibonacci heap.
Furthermore, the abuse of Big-O notation in the final sentence is mathematically glaring. By stating that Prim's beats Kruskal's when is a classic trap of treating Big-O as an exact tight bound rather than an upper bound. If then the conclusion is obviously false.
★ 21.2-8
Introduces an algorithm for incrementally updating an MST upon adding a new vertex and incident edges to graph whose MST is known.
Available in the latest revision of the IM.
Problems
21-1 Second-best minimum spanning tree
Available in the latest revision of the IM.
21-2 Minimum spanning tree in sparse graphs
a.
By Exercise 21.1-1, we know that the edges added to are safe. Furthermore, the correctness of the Prim's algorithm ensures that is an MST of . The edges in and are disjoint by construction.
We claim that their union is an MST of . Suppose there exists some other spanning tree for such that . Because the edges in are safe, we can assume contains all the edges in . If we take and contract all the edges belonging to (just like the algorithm does to create ), the remaining edges of must form a spanning tree for . Let's call this contracted remainder . The total weight of the hypothetical better tree is . We assumed this is less than . Subtracting from both sides leaves us with . But this is impossible!
Notice that is a tree, since vertices of represent sets of connected vertices of via and connects these hyper-vertices.
b.
This follows from the way how MST-Reduce works. Each unmarked vertex of is placed into the same vertex set where belongs for a selected minimum-weight edge . Therefore, every hyper-vertex of represents a vertex set of size at least 2. Furthermore, all such sets of vertices are disjoint.
c.
Here is the implementation that augments the base variant:
Component Labeling (Replacing Disjoint Sets)
Instead of maintaining a disjoint-set data structure, we can select all the safe edges first and then use a simple graph traversal to identify the hyper-vertices.
Select Edges: Iterate through the original vertices . For each unmarked vertex , scan its adjacency list to find the minimum-weight incident edge . Add this edge to a list and mark both and . Since we only scan the adjacency list of each vertex once to find its minimum edge, this entire process takes time.
Find Components ( time): Create a temporary graph using the original vertices and only the edges in . Run a standard BFS or DFS on this graph to find its connected components.
Assign IDs ( time): During the traversal, assign a unique integer ID (from 1 to , where by part (b)) to each connected component. Store this in an array
comp[x] = IDfor every original vertex . This array perfectly replaces theFIND-SET(x)operation and provides lookups.
Resolving Parallel Edges
Now we need to map the original edges to the new hyper-vertices and filter out duplicate (parallel) edges, keeping only the lightest ones.
Map Edges ( time): Iterate through all original edges . Look up their new endpoints:
u = comp[x]andv = comp[y]. If , we have a valid edge for . Store it in a flat array as a tuple: . Notice that we also store the original edge.Radix Sort ( time): We need to group edges that connect the same pair of hyper-vertices. Because the hyper-vertex IDs are integers bounded by , we can sort the list of tuples using Radix Sort (specifically, two passes of Counting Sort: first on the coordinate, then on the coordinate). Radix Sort takes time, which simplifies to since in a connected graph.
Filter Duplicates ( time): Because the list is now sorted lexicographically by endpoint pairs, any duplicate edges between the same two hyper-vertices will be adjacent in the array. Do a single linear scan through the sorted array. For each block of identical endpoint pairs, select the one with the minimum weight, add it to , and discard the rest.
d.
Observe that by part (b) and . Each phase demands time by part (c), which gives total time.
e.
By part (b), each phase reduces the number of vertices by at least half. After phases, the contracted graph has vertices. The number of edges remains bounded by . Combining these, the total asymptotic running time is
If we set , then Because we are working with a single connected component, we know , which justifies the last step.
This hybrid algorithm is specifically designed for sparse graphs (where ), which is where standard Prim's algorithm gets bottlenecked by its term. Our running time function represents a classic trade-off:
As increases, the term grows, but the term shrinks.
As decreases, the term shrinks, but the term grows.
To find the minimum asymptotic running time, we must balance these two competing terms so that neither dominates and balloons the overall complexity.
What if is smaller? If we pick a smaller , say (meaning is asymptotically smaller), the second term becomes , which easily overtakes for sparse graphs, worsening the time complexity.
What if is larger? If we pick a larger , say , the first term becomes , which exceeds our bound.
Therefore, setting is the precise "sweet spot" where the cost of contracting vertices exactly neutralizes the expensive priority-queue operations of Prim's algorithm, yielding the absolute lowest upper bound.
f.
Let's set up the inequality . Notice that the term on the left will always be asymptotically larger than the standalone term on the right. Therefore, for the left side to win, the term must be the dominant force on the right side, and it must be larger than . Thus,
21-3 Alternative minimum-spanning-tree algorithms
Available in the latest revision of the IM.
21-4 Bottleneck spanning tree
a.
Suppose is an MST, whose heaviest edge is , but not a bottleneck spanning tree. This means there exists some other spanning tree, let's call it (the actual bottleneck tree), whose maximum edge weight is strictly less than . If we remove from , it splits it into two disconnected components, creating a cut . Because is a valid spanning tree of the whole graph , it must connect and . Therefore, must contain at least one edge that crosses this exact cut. Since , its weight can be no larger than the maximum edge in . From our initial assumption, we know the maximum edge of is strictly less than . Now we can safely construct . Because crosses the cut created by removing , it perfectly reconnects the two components. is a valid spanning tree with. This contradicts the fact that is a minimum spanning tree.
Thus, a minimum spanning tree is a bottleneck spanning tree.
b.
If a bottleneck spanning tree has a value of at most , it means there is some way to connect all the vertices in the graph using only edges that have a weight of or less. If we simply throw away all edges heavier than , we just need to check if the remaining graph is still connected. This can be done by counting the number of vertices visited via BFS or DFS. We can solve this task in linear time.
c.
Let be a connected, undirected graph as input to our algorithm. A graph is only connected if , so .
Test
Using part (b) as a subroutine, check if the graph is connected using only the edges in . There are two possible outcomes:
Test is negative: This means it is impossible to span the graph using edges of weight or less. The bottleneck edge must be strictly greater than . Because we have to use heavier edges to connect the graph, all the edges in are "safe" to use without exceeding our eventual bottleneck. Just like in
MST-Reduce, find the connected components formed by these edges and contract each component into a single hyper-vertex. Recurse on this newly contracted graph using only the edges in .Test is positive: This means the bottleneck is . We can safely throw away all edges in . But we still need to know if the bottleneck is exactly , or if it's strictly less than . Run the test again, but this time only use the edges in .
If positive: The bottleneck is strictly less than . Throw away as well, and recurse on the original vertices using only .
If negative: The bottleneck is exactly . Stop and return the corresponding DFS tree.
The time complexity forms the recurrence relation , which evaluates to a strictly linear total time.
Last updated