The algorithm
- Sort the edges by weight, breaking ties any way at all.
- Put every vertex in its own piece.
- For each edge in order, ask whether its two ends are in the same piece. If they are, reject it. If they are not, take it and merge the two pieces.
- Stop when V - 1 edges have been taken, or when the list runs out — in which case the graph was not connected and what was built is a minimum spanning forest.
The operations on pieces are exactly the two a disjoint-set structure provides, and Data Structures' “Union–Find” costs them. This lesson uses the operations and re-derives none of their analysis; what it does measure is how many of each this algorithm performs, which is a property of the algorithm rather than of the structure.
Theorem · Kruskal's algorithm returns a minimum spanning treeOn a connected weighted graph, the set of edges the algorithm takes is a spanning tree of least total weight.
It is a forest at every step, because an edge is taken only when its ends are in different pieces, which is exactly the condition for it not to close a cycle. It ends spanning: if two pieces remained, connectivity gives an edge between them, and that edge would have been taken when the list reached it.
It is minimum by induction on the number of edges taken, with the invariant that the current forest is contained in some minimum spanning tree. Empty at the start, so the invariant holds. Suppose it holds before an edge e is taken, with the forest inside a minimum tree T. Let S be the piece containing one end of e. No edge of the forest crosses that cut, and every crossing edge is either unexamined — hence at least as heavy as e — or was rejected, which cannot happen to a crossing edge while S is a piece. So e is a lightest crossing edge, and by the exchange in the cut property there is a minimum tree containing the forest and e. The invariant survives, and at the end the forest is a spanning tree inside a minimum tree, so it is one.
□
Example · Nine edges, six taken, three rejectedThe lab opens on 1-2 4, 1-3 3, 2-3 2, 2-4 5, 3-4 7, 4-5 1, 5-6 6, 5-7 8, 6-7 2. Sorted, the list is 4-5 at 1, 2-3 at 2, 6-7 at 2, 1-3 at 3, 1-2 at 4, 2-4 at 5, 5-6 at 6, 3-4 at 7, 5-7 at 8. The algorithm takes the first four, rejects 1-2, takes 2-4 and 5-6, then rejects 3-4 and 5-7. Total weight 19, on six edges for seven vertices. The counters read 18 finds, 6 unions and 10 pointer hops. An independently written Kruskal, from another Subject and with its own representation, is handed the same graph and returns 19.
Read the rejections. 1-2 at weight 4 is refused because 1 and 2 are already joined through 3, by the edges 2-3 at 2 and 1-3 at 3, and 4 is heavier than both — it is the heaviest edge on that triangle. 3-4 and 5-7 are refused for the same reason on their own cycles. No rejected edge is ever reconsidered, and the cycle property is why that is sound rather than greedy.
What the three counters are for
Finds are two per edge examined, so 18 on nine edges, whatever the graph looks like. Unions are one per taken edge, so V - 1 on a connected graph, so 6 here. Neither of those is interesting on its own. The hops are: they count how far each find had to walk up the parent pointers, and that depends on how the unions happened to be arranged.
The lab's chain preset makes this visible. On 1-2 1, 2-3 2, 3-4 3, 4-5 4, 5-6 5, 1-6 9 the unions build a line, and the counters read 12 finds, 5 unions and 5 hops for weight 15. The complete graph on five vertices goes the other way: ten edges, four taken and six rejected, 20 finds, 4 unions and 18 hops for weight 7. The hop count is the only one of the three that moved for a reason other than the number of edges.
This implementation deliberately does neither union by rank nor path compression, so the hop count is a real measurement of an unassisted structure. With those two refinements the finds and unions would be identical and the hops would collapse, which is precisely the point: the refinements change the cost of the structure and not the behaviour of the algorithm.
When the graph is not connected
On 1-2 1, 2-3 2, 4-5 3, 5-6 4 the list runs out after four edges are taken, one short of the five a spanning tree of six vertices needs. The panel reports that it does not span, rather than presenting a forest of weight 10 as though it were a tree. The result is still the minimum spanning forest, and it is still the right answer to a slightly different question — but the difference is exactly the kind a caller has to be told about.
Measurement beside bound, last time. On the opening graph: 18 finds, 6 unions, 10 hops, and a sort of nine items. Those are counts for that graph. The bound is O(E log V) on every graph and it is dominated by the sort: the finds are 2E exactly, the unions are at most V - 1 exactly, and the hops are what the disjoint-set analysis bounds. The measured hop count on the complete graph — 18, on twenty finds — is evidence about an unassisted structure on one input, and nothing more.