Foundations

Prim vs Kruskal vs Borůvka

Prim, Kruskal and Borůvka all find a minimum spanning tree, but they build it in very different orders. This guide runs all three on one example graph, shows why each is correct, compares their speed with measured step counts on sparse and dense graphs, and covers ties, disconnected graphs, clustering and the difference from Dijkstra's algorithm, with tested Python code throughout.

21 Min Read Updated: November 2026 Beginner Level
Mohammed Islam Hadjoudj
Mohammed Islam Hadjoudj
Expert Operations Research Engineer

1. The short answer

Prim's algorithm, Kruskal's algorithm and Borůvka's algorithm all find a minimum spanning tree: the cheapest set of edges that connects every vertex of a weighted graph. They build it in different orders, and that decides which one suits a given graph. The guide to minimum spanning trees introduces the problem itself.

PrimKruskalBorůvka
StrategyGrow one tree outward from a start vertexTake edges from cheapest up, skipping any that close a cycleEvery group picks its cheapest outgoing edge, all at once
Main data structurePriority queue, or a plain arraySorted edge list and union-findUnion-find, or relabelling
TimeO(E log V) with a binary heap, O(V2) with an arrayO(E log E), nearly linear if the edges are already sortedO(E log V)
Natural inputAdjacency list or matrixEdge listEdge list
Disconnected graphCovers only the start vertex's componentGives a spanning forestGives a spanning forest
Best forDense graphs, with the array versionSparse graphs, presorted edges, clusteringParallel and distributed computing

Here V is the number of vertices and E the number of edges. In one sentence: use Kruskal when the graph is sparse or given as a list of edges, use Prim when it is dense or given as a matrix, and consider Borůvka when the work must be split across many processors. On a sparse graph Kruskal and Prim with a heap cost about the same, so the simpler code for your data is the right choice.

A decision chart. First question: is the graph connected? If not, use Kruskal or Borůvka, which produce a spanning forest. If it is: are the edges already sorted or given as a list? If yes, Kruskal. If no: is the graph dense? If yes, Prim with an array; if no, Prim with a heap or Kruskal. A side note says to use Borůvka when the work must run in parallel, and that all three give the same total weight.
The shape of the graph and the form of the input pick the algorithm. All three return a tree of the same total weight.

2. One graph, three runs

Eight villages, A to H, want to be joined by water pipes. There are 14 possible routes, each with a cost in kilometres of trench, and every village must end up connected to every other. Together, all 14 routes would cost 117.

A-B 3    A-C 6    B-C 4    B-D 13   C-D 7    C-E 12   D-E 2
D-F 11   E-F 8    E-G 14   E-H 5    F-G 1    F-H 15   G-H 16

8 villages, 14 possible pipes

A cheapest connection uses exactly 7 pipes, one fewer than the number of villages, because any extra pipe would close a loop that could be removed to save money. This graph has 712 spanning trees, and exactly one of them costs the minimum of 30. The program below implements all three algorithms and runs them on it.

import heapq

PIPES = [('A', 'B', 3), ('A', 'C', 6), ('B', 'C', 4), ('B', 'D', 13), ('C', 'D', 7),
         ('C', 'E', 12), ('D', 'E', 2), ('D', 'F', 11), ('E', 'F', 8), ('E', 'G', 14),
         ('E', 'H', 5), ('F', 'G', 1), ('F', 'H', 15), ('G', 'H', 16)]

class Groups:
    """Union-find: which group is each vertex in?"""
    def __init__(self, items):
        self.parent = {x: x for x in items}
    def find(self, x):
        while self.parent[x] != x:
            self.parent[x] = self.parent[self.parent[x]]   # path halving
            x = self.parent[x]
        return x
    def union(self, a, b):
        ra, rb = self.find(a), self.find(b)
        if ra == rb:
            return False
        self.parent[ra] = rb
        return True

def vertices(edges):
    return sorted({x for u, v, _ in edges for x in (u, v)})

def prim(edges, start):
    """Grow one tree: always add the cheapest edge that leaves it."""
    adj = {v: [] for v in vertices(edges)}
    for u, v, w in edges:
        adj[u].append((w, u, v))
        adj[v].append((w, v, u))
    seen, heap, tree = {start}, list(adj[start]), []
    heapq.heapify(heap)
    while heap:
        w, u, v = heapq.heappop(heap)
        if v in seen:
            continue                                  # both ends are already in the tree
        seen.add(v)
        tree.append((u, v, w))
        for e in adj[v]:
            if e[2] not in seen:
                heapq.heappush(heap, e)
    return tree

def kruskal(edges):
    """Take the edges from cheapest up, skipping any that would close a cycle."""
    groups, tree = Groups(vertices(edges)), []
    for u, v, w in sorted(edges, key=lambda e: e[2]):
        if groups.union(u, v):
            tree.append((u, v, w))
    return tree

def boruvka(edges):
    """In each round, every group picks its cheapest outgoing edge at the same time."""
    groups, tree, rounds = Groups(vertices(edges)), [], []
    while True:
        cheapest = {}
        for u, v, w in edges:
            ru, rv = groups.find(u), groups.find(v)
            if ru != rv:
                for r in (ru, rv):                    # ties broken the same way everywhere
                    if r not in cheapest or (w, u, v) < cheapest[r]:
                        cheapest[r] = (w, u, v)
        if not cheapest:
            return tree, rounds
        added = []
        for w, u, v in sorted(set(cheapest.values())):
            if groups.union(u, v):
                added.append((u, v, w))
        tree += added
        rounds.append(added)

def show(tree):
    return ' '.join(f'{u}{v}:{w}' for u, v, w in tree) + f'   total {sum(w for *_, w in tree)}'

print('Prim    ', show(prim(PIPES, 'A')))
# Prim     AB:3 BC:4 CD:7 DE:2 EH:5 EF:8 FG:1   total 30
print('Kruskal ', show(kruskal(PIPES)))
# Kruskal  FG:1 DE:2 AB:3 BC:4 EH:5 CD:7 EF:8   total 30
tree, rounds = boruvka(PIPES)
print('Boruvka ', show(tree))
# Boruvka  FG:1 DE:2 AB:3 BC:4 EH:5 CD:7 EF:8   total 30
for i, added in enumerate(rounds, 1):
    print('round', i, show(added))
# round 1 FG:1 DE:2 AB:3 BC:4 EH:5   total 15
# round 2 CD:7 EF:8   total 15
Three copies of the village graph. Prim from A adds its edges in the order A-B, B-C, C-D, D-E, E-H, E-F, F-G, growing one connected tree. Kruskal adds F-G, D-E, A-B, B-C, E-H, rejects A-C because it would close a cycle, then adds C-D and E-F. Borůvka adds F-G, D-E, A-B, B-C and E-H together in round 1, forming three groups, and C-D and E-F in round 2. All three end with the same tree of cost 30.
Three orders, one tree. Prim's edges always form a single connected piece; Kruskal's and Borůvka's start as scattered fragments that merge.

The three orders tell the story of each method:

3. Why all three are correct

Different as they look, all three rest on the same fact about cuts. A cut divides the vertices into two groups, as the guide to the minimum cut explains. The cut property says: for any cut, the cheapest edge crossing it belongs to a minimum spanning tree, and when weights are all different it belongs to every one. The proof is an exchange. Take a spanning tree that does not use that edge and add it: this closes a cycle, the cycle must cross the cut a second time on some dearer edge, and removing that dearer edge gives a cheaper tree.

Left, the cut property: the villages A, B and C are shaded and the edges leaving them are B-D at 13, C-D at 7 and C-E at 12. The cheapest, C-D at 7, must be in the tree. Right, the cycle property: the triangle A, B, C has edges of cost 3, 4 and 6. The most expensive, A-C at 6, can be left out of the tree.
The cheapest edge across a cut is safe to take. The dearest edge on a cycle is safe to discard. Every minimum spanning tree algorithm applies one or both rules.

Each algorithm applies this rule to a different cut:

The mirror image is the cycle property: the most expensive edge on any cycle can be left out of a minimum spanning tree. That is Kruskal's rejection step. When A-C at 6 would close the cycle A, B, C, it is the dearest edge on that cycle, because every other edge on it was taken earlier. A minimum spanning tree never needs that edge.

4. When the trees differ

If all edge weights are different, the minimum spanning tree is unique, so all three algorithms must return exactly the same edges, as they do on the example. With equal weights there may be several minimum spanning trees, all with the same total, and which one you get depends on the algorithm and even on the order of the input. Change A-C to cost 4, the same as B-C, and there are exactly two minimum spanning trees:

PIPES = [('A', 'B', 3), ('A', 'C', 6), ('B', 'C', 4), ('B', 'D', 13), ('C', 'D', 7),
         ('C', 'E', 12), ('D', 'E', 2), ('D', 'F', 11), ('E', 'F', 8), ('E', 'G', 14),
         ('E', 'H', 5), ('F', 'G', 1), ('F', 'H', 15), ('G', 'H', 16)]

def kruskal(edges):
    parent = {x: x for u, v, _ in edges for x in (u, v)}
    def find(x):
        while parent[x] != x:
            x = parent[x]
        return x
    tree = []
    for u, v, w in sorted(edges, key=lambda e: e[2]):  # stable: ties keep their input order
        if find(u) != find(v):
            parent[find(u)] = find(v)
            tree.append(u + v)
    return sorted(tree), sum(w for u, v, w in edges if u + v in tree)

tied = [(u, v, 4 if (u, v) == ('A', 'C') else w) for u, v, w in PIPES]   # A-C now costs 4, like B-C
print(kruskal(tied))
# (['AB', 'AC', 'CD', 'DE', 'EF', 'EH', 'FG'], 30)
print(kruskal(tied[::-1]))
# (['AB', 'BC', 'CD', 'DE', 'EF', 'EH', 'FG'], 30)

Both trees cost 30 and differ in one pipe. Python's sort is stable, so equal edges keep their input order, and reversing the list changes which of the two is taken first. If your tests compare the exact edges of a tree, they can fail on a correct program; compare the total weight instead.

Ties are also the classic bug in Borůvka's algorithm. When two groups each choose between edges of equal weight, inconsistent choices can create a cycle. The program above avoids this by comparing (w, u, v) everywhere, so every group breaks ties the same way.

5. Speed: which one is faster, and when

VersionTimeWhere the time goes
Prim, binary heapO(E log V)Up to one heap operation per edge
Prim, plain arrayO(V2)Scanning all remaining vertices, once per vertex added
Prim, Fibonacci heapO(E + V log V)Fast theoretically, heavy constants in practice
KruskalO(E log E)Sorting; the union-find work after that is almost linear
BorůvkaO(E log V)At most log2 V rounds, each scanning every edge

Since E is at most V2, log E is at most 2 log V, so O(E log E) and O(E log V) are the same class. The formulas alone do not separate the methods, so the program below counts basic steps, meaning comparisons, edge inspections and union-find hops, on two random graphs with 1,000 vertices. One is sparse, with 3,000 edges, and one dense, with 249,750 edges, half of all possible pairs.

import heapq, random
from functools import cmp_to_key

def random_graph(n, m, seed):
    """A connected graph: a random spanning tree plus random extra edges, weights all different."""
    rnd = random.Random(seed)
    order = list(range(n))
    rnd.shuffle(order)
    pairs = {tuple(sorted((order[i], order[rnd.randrange(i)]))) for i in range(1, n)}
    while len(pairs) < m:
        u, v = rnd.sample(range(n), 2)
        pairs.add((min(u, v), max(u, v)))
    weights = rnd.sample(range(1, 10 * m), m)
    return [(u, v, w) for (u, v), w in zip(sorted(pairs), weights)]

class Item:
    """A heap entry that counts how often the heap compares it."""
    compared = 0
    def __init__(self, w, v):
        self.w, self.v = w, v
    def __lt__(self, other):
        Item.compared += 1
        return self.w < other.w

def prim_heap(n, edges):
    adj = [[] for _ in range(n)]
    for u, v, w in edges:
        adj[u].append((w, v))
        adj[v].append((w, u))
    Item.compared = 0
    seen, steps = {0}, len(adj[0])
    heap = [Item(w, v) for w, v in adj[0]]
    heapq.heapify(heap)
    while heap:
        item = heapq.heappop(heap)
        if item.v in seen:
            continue
        seen.add(item.v)
        for w, v in adj[item.v]:
            steps += 1                                # one edge inspected
            if v not in seen:
                heapq.heappush(heap, Item(w, v))
    return steps + Item.compared

def prim_array(n, edges):
    """Prim without a heap: scan the best known cost of every remaining vertex."""
    adj = [[] for _ in range(n)]
    for u, v, w in edges:
        adj[u].append((w, v))
        adj[v].append((w, u))
    best, done, steps = [float('inf')] * n, [False] * n, 0
    best[0] = 0
    for _ in range(n):
        left = [v for v in range(n) if not done[v]]
        u = min(left, key=best.__getitem__)
        steps += len(left)
        done[u] = True
        for w, v in adj[u]:
            steps += 1
            if not done[v] and w < best[v]:
                best[v] = w
    return steps

def kruskal(n, edges):
    compared = [0]
    def cmp(a, b):
        compared[0] += 1
        return a[2] - b[2]
    parent, steps = list(range(n)), 0
    def find(x):
        nonlocal steps
        while parent[x] != x:
            steps += 1
            parent[x] = parent[parent[x]]
            x = parent[x]
        steps += 1
        return x
    for u, v, w in sorted(edges, key=cmp_to_key(cmp)):
        ru, rv = find(u), find(v)
        if ru != rv:
            parent[ru] = rv
    return compared[0] + steps

def boruvka(n, edges):
    parent, steps = list(range(n)), 0
    def find(x):
        nonlocal steps
        while parent[x] != x:
            steps += 1
            parent[x] = parent[parent[x]]
            x = parent[x]
        steps += 1
        return x
    while True:
        cheapest = {}
        for u, v, w in edges:
            ru, rv = find(u), find(v)
            if ru != rv:
                for r in (ru, rv):
                    steps += 1
                    if r not in cheapest or w < cheapest[r][2]:
                        cheapest[r] = (u, v, w)
        if not cheapest:
            return steps
        for u, v, w in set(cheapest.values()):
            ru, rv = find(u), find(v)
            if ru != rv:
                parent[ru] = rv

n = 1000
for name, m in [('sparse', 3 * n), ('dense', n * (n - 1) // 4)]:
    edges = random_graph(n, m, seed=1)
    print(f'{name:6} E={m:6,}  Prim heap {prim_heap(n, edges):9,}  Prim array {prim_array(n, edges):9,}  '
          f'Kruskal {kruskal(n, edges):9,}  Boruvka {boruvka(n, edges):9,}')
# sparse E= 3,000  Prim heap    46,360  Prim array   506,500  Kruskal    44,414  Boruvka    73,486
# dense  E=249,750  Prim heap 5,231,906  Prim array 1,000,000  Kruskal 5,151,232  Boruvka 7,417,850
A bar chart on a logarithmic scale of the steps taken on random graphs with 1,000 vertices. On the sparse graph with 3,000 edges: Prim with a heap 46,360, Prim with an array 506,500, Kruskal 44,414, Borůvka 73,486. On the dense graph with 249,750 edges: Prim with a heap 5,231,906, Prim with an array 1,000,000, Kruskal 5,151,232, Borůvka 7,417,850.
On the sparse graph the array version of Prim is the slowest by far. On the dense graph it is the fastest, about five times ahead of the others.

The results flip with density. On the sparse graph, Kruskal and Prim with a heap are within 5% of each other, Borůvka needs about 60% more, and array-based Prim is about 11 times slower, because it scans every remaining vertex even though each has few edges. On the dense graph the array version takes exactly 1,000,000 steps and wins by a factor of about five: it never has more than V candidates to look at, while the heap and the sort pay a logarithmic cost on nearly 250,000 edges.

These numbers count steps, not seconds. Sorting an array of numbers is very fast on real hardware, which often favours Kruskal, and the heap version here is the simple one that may hold several entries per vertex. A version that updates entries in place keeps the heap smaller at the price of more code. If the time matters, measure on your own data.

6. The shape of your input

Often the input decides before any complexity analysis does. The guide to graph representation compares the formats.

Kruskal's union-find structure is worth understanding on its own, and the guide to union-find covers it in depth.

7. Disconnected graphs and stopping early

The three methods also differ in what they do when the graph is not one piece, and in what they can tell you if you stop early.

Disconnected graphs. Prim only reaches the connected component of its start vertex; to cover the whole graph it has to be restarted in each component. Kruskal and Borůvka need no change: they simply run out of edges and return a minimum spanning forest, one tree per component.

Clustering. Kruskal's intermediate states are useful in themselves. Stop when k groups remain and those groups are the single-linkage clusters of the data: the division into k groups in which the closest pair of points from different groups is as far apart as possible.

PIPES = [('A', 'B', 3), ('A', 'C', 6), ('B', 'C', 4), ('B', 'D', 13), ('C', 'D', 7),
         ('C', 'E', 12), ('D', 'E', 2), ('D', 'F', 11), ('E', 'F', 8), ('E', 'G', 14),
         ('E', 'H', 5), ('F', 'G', 1), ('F', 'H', 15), ('G', 'H', 16)]

def kruskal_until(edges, groups_wanted):
    """Kruskal, stopped as soon as only groups_wanted groups remain."""
    parent = {x: x for u, v, _ in edges for x in (u, v)}
    def find(x):
        while parent[x] != x:
            parent[x] = parent[parent[x]]
            x = parent[x]
        return x
    count, tree = len(parent), []
    for u, v, w in sorted(edges, key=lambda e: e[2]):
        if count == groups_wanted:
            break
        if find(u) != find(v):
            parent[find(u)] = find(v)
            tree.append((u, v, w))
            count -= 1
    members = {}
    for x in sorted(parent):
        members.setdefault(find(x), []).append(x)
    return tree, sorted(''.join(m) for m in members.values())

print(kruskal_until(PIPES, 3)[1])              # three clusters of villages
# ['ABC', 'DEH', 'FG']
print(kruskal_until(PIPES, 2)[1])
# ['ABCDEH', 'FG']

# A graph in two pieces: drop every pipe between A to E and F, G, H.
split = [e for e in PIPES if not (set(e[:2]) & set('FGH')) or set(e[:2]) <= set('FGH')]
tree, parts = kruskal_until(split, 1)          # asks for 1 group, but only 2 can exist
print(parts, sum(w for *_, w in tree))
# ['ABCDE', 'FGH'] 32

Stopped at three groups, the program returns {A, B, C}, {D, E, H} and {F, G}, the same three that Borůvka's first round produced on this graph, although that agreement is a coincidence of the example. In the last run, with the pipes between the two halves removed, Kruskal returns a forest of two trees costing 32. Prim from A would stop after five villages and never see F, G or H.

8. Negative weights, maximum trees and directed graphs

9. Prim is not Dijkstra

Prim's algorithm and Dijkstra's look almost identical in code, and the difference is one line: Prim orders the queue by the weight of the single edge reaching a vertex, while Dijkstra orders it by the total distance from the source. They optimise different things and build different trees. The guide to Prim's algorithm compares them line by line.

On the example, Dijkstra's shortest-path tree from A uses A-C at 6 instead of B-C at 4. It costs 32 in total, against 30 for the minimum spanning tree. In return, every village is at least as close to A along it: C is 6 away instead of 7, H 20 instead of 21 and G 24 instead of 25. A minimum spanning tree minimises the cost of building the network and says nothing about the length of any single route.

10. A tangled history

The three names are misleading about who came first. The earliest algorithm is Borůvka's. In 1926 the Czech mathematician Otakar Borůvka published it after Jindřich Saxel, an employee of the West Moravian power company, asked him in the winter of 1925 how to electrify south-west Moravia as cheaply as possible. Vojtěch Jarník then described what is now called Prim's algorithm in 1930, in a paper written as a letter to Borůvka. Joseph Kruskal published his method in 1956, and Robert Prim rediscovered Jarník's algorithm in 1957 at Bell Laboratories, as did Edsger Dijkstra in 1959. Prim's algorithm is therefore sometimes called Jarník's algorithm or the DJP algorithm, although "Prim's" remains by far the most common name. Borůvka's method was itself rediscovered several times, and in some books it carries the name of Sollin, who presented it at a seminar in Paris in 1961 and never published it.

The fastest known methods have moved far from these classics. David Karger, Philip Klein and Robert Tarjan gave a randomized algorithm with expected linear time in 1995. Bernard Chazelle gave a deterministic algorithm in 2000 that runs in O(E α(E, V)), where α is the inverse Ackermann function, which is at most 4 for any graph that could ever be stored. In 2002 Seth Pettie and Vijaya Ramachandran found an algorithm that is provably optimal, although nobody knows what its running time actually is: it lies somewhere between linear and Chazelle's bound. Karger, Klein and Tarjan's algorithm uses Borůvka's rounds as a building block.

11. Where each one is used

12. Common mistakes

MistakeWhy it is wrongFix
Using Prim on a disconnected graph and trusting the resultIt only spans the start vertex's componentRestart it in each component, or use Kruskal
Assuming Prim with a heap is always bestOn the dense graph the array version took five times fewer stepsUse the array version when E is close to V2
Treating a minimum spanning tree as a set of shortest pathsIn the example it reaches C at 7, but the shortest route costs 6Use Dijkstra for routes, a spanning tree for build cost
Testing for one exact tree when weights can tieSeveral minimum trees can exist, and the input order picks oneCompare the total weight
Breaking ties inconsistently in BorůvkaGroups may choose edges that form a cycleCompare by weight, then by a fixed edge order
Avoiding the algorithms because of negative weightsNegative weights do not affect any of the threeRun them unchanged
Running them on a directed graphThe cut property is about undirected edgesUse the Chu-Liu/Edmonds algorithm
Letting Kruskal scan every edgeThe tree is complete after V - 1 accepted edgesStop as soon as you have them

13. Glossary

TermMeaning
Spanning treeA tree that contains every vertex of the graph
Minimum spanning treeA spanning tree of smallest total weight
Spanning forestOne spanning tree for each connected component
Cut propertyThe cheapest edge crossing any cut can be in a minimum spanning tree
Cycle propertyThe most expensive edge on any cycle can be left out
Union-findA structure that tracks groups and merges them quickly
Priority queueA structure that always returns the smallest key; usually a binary heap
Dense graphA graph in which a large fraction of all vertex pairs are joined by edges
Sparse graphA graph with few edges compared with V2, typically a small multiple of V
Single-linkage clusteringGrouping by repeatedly joining the two closest groups
Bottleneck edgeThe heaviest edge of a tree
ArborescenceA directed tree in which every edge points away from one root

14. Frequently asked questions

What is the difference between Prim's and Kruskal's algorithm?

Prim's algorithm grows one connected tree from a start vertex, always adding the cheapest edge that leaves it. Kruskal's algorithm sorts all the edges and takes them from cheapest up, skipping any edge that would close a cycle, so its tree starts as scattered fragments that merge. Both find a minimum spanning tree of the same total weight.

Which is better, Prim or Kruskal?

It depends on the graph. Kruskal is the natural choice for sparse graphs and for edges given as a list or already sorted. Prim with a plain array is the natural choice for dense graphs, since it runs in O(V squared) and does not need to sort the edges. On sparse graphs, Prim with a binary heap and Kruskal cost about the same.

Do Prim and Kruskal give the same minimum spanning tree?

If all edge weights are different, the minimum spanning tree is unique and both return exactly the same edges. If some weights are equal, there may be several minimum spanning trees, and the two algorithms can return different ones, but they always have the same total weight.

What is Borůvka's algorithm?

Borůvka's algorithm works in rounds. In each round, every group of connected vertices picks its cheapest edge to another group, and all those edges are added at once. The number of groups at least halves each round, so there are at most log2 V rounds and the running time is O(E log V). Because the groups work independently, it is well suited to parallel computing.

Can Prim and Kruskal handle negative edge weights?

Yes. Unlike Dijkstra's algorithm, Prim, Kruskal and Borůvka all work correctly with negative weights. Every spanning tree has exactly V - 1 edges, so adding the same constant to every weight changes the cost of every tree equally and does not change which tree is cheapest.

What is the difference between Prim's algorithm and Dijkstra's algorithm?

Their code is almost identical, but Prim orders vertices by the weight of the single edge reaching them, while Dijkstra orders them by total distance from the source. Prim builds the tree of least total weight. Dijkstra builds a tree of shortest paths from one source, which can cost more in total but gives shorter routes from that source.

15. References

The algorithms, bounds and attributions above come from these sources, listed in chronological order.

  1. Borůvka, O. (1926). "O jistém problému minimálním." Práce Moravské přírodovědecké společnosti 3, 37 to 58.
  2. Jarník, V. (1930). "O jistém problému minimálním (Z dopisu panu O. Borůvkovi)." Práce Moravské přírodovědecké společnosti 6, 57 to 63.
  3. Kruskal, J. B. (1956). "On the Shortest Spanning Subtree of a Graph and the Traveling Salesman Problem." Proceedings of the American Mathematical Society 7(1), 48 to 50.
  4. Prim, R. C. (1957). "Shortest Connection Networks and Some Generalizations." Bell System Technical Journal 36(6), 1389 to 1401.
  5. Dijkstra, E. W. (1959). "A Note on Two Problems in Connexion with Graphs." Numerische Mathematik 1, 269 to 271.
  6. Chu, Y.-J. and Liu, T.-H. (1965). "On the Shortest Arborescence of a Directed Graph." Scientia Sinica 14, 1396 to 1400.
  7. Edmonds, J. (1967). "Optimum Branchings." Journal of Research of the National Bureau of Standards 71B, 233 to 240.
  8. Gower, J. C. and Ross, G. J. S. (1969). "Minimum Spanning Trees and Single Linkage Cluster Analysis." Journal of the Royal Statistical Society, Series C 18(1), 54 to 64.
  9. Tarjan, R. E. (1975). "Efficiency of a Good But Not Linear Set Union Algorithm." Journal of the ACM 22(2), 215 to 225.
  10. Christofides, N. (1976). Worst-Case Analysis of a New Heuristic for the Travelling Salesman Problem. Report 388. Pittsburgh: Carnegie Mellon University.
  11. Graham, R. L. and Hell, P. (1985). "On the History of the Minimum Spanning Tree Problem." Annals of the History of Computing 7(1), 43 to 57.
  12. Fredman, M. L. and Tarjan, R. E. (1987). "Fibonacci Heaps and Their Uses in Improved Network Optimization Algorithms." Journal of the ACM 34(3), 596 to 615.
  13. Karger, D. R., Klein, P. N. and Tarjan, R. E. (1995). "A Randomized Linear-Time Algorithm to Find Minimum Spanning Trees." Journal of the ACM 42(2), 321 to 328.
  14. Chazelle, B. (2000). "A Minimum Spanning Tree Algorithm with Inverse-Ackermann Type Complexity." Journal of the ACM 47(6), 1028 to 1047.
  15. Nešetřil, J., Milková, E. and Nešetřilová, H. (2001). "Otakar Borůvka on Minimum Spanning Tree Problem." Discrete Mathematics 233(1 to 3), 3 to 36.
  16. Pettie, S. and Ramachandran, V. (2002). "An Optimal Minimum Spanning Tree Algorithm." Journal of the ACM 49(1), 16 to 34.
  17. Felzenszwalb, P. F. and Huttenlocher, D. P. (2004). "Efficient Graph-Based Image Segmentation." International Journal of Computer Vision 59(2), 167 to 181.
  18. Kleinberg, J. and Tardos, É. (2005). Algorithm Design. Boston: Addison-Wesley.
  19. Bader, D. A. and Cong, G. (2006). "Fast Shared-Memory Algorithms for Computing the Minimum Spanning Forest of Sparse Graphs." Journal of Parallel and Distributed Computing 66(11), 1366 to 1378.
  20. Cormen, T. H., Leiserson, C. E., Rivest, R. L. and Stein, C. (2009). Introduction to Algorithms, 3rd edition. Cambridge, MA: MIT Press.
  21. Osipov, V., Sanders, P. and Singler, J. (2009). "The Filter-Kruskal Minimum Spanning Tree Algorithm." In Proceedings of the 11th Workshop on Algorithm Engineering and Experiments, 52 to 61.

Watch the tree grow

Build the eight villages in the visualizer and run Prim from A. Then run Kruskal on the same graph and compare the order in which the edges appear.

Open the visualizer

Sort the Edges Yourself

Run Kruskal step by step on your own graph and watch each edge be accepted or rejected. Then run Prim on the same graph and see the same total appear in a different order.

Launch the Kruskal Visualizer