Raw

Lecture 11. Graph theory — algorithms

11.1 Overview

Lecture 10 developed the static language of graphs: vertices and edges, degrees, adjacency representations, weighted graphs, and the two coloring invariants — the chromatic number χ(G)\chi(G) (fewest colors for a proper vertex coloring) and the chromatic index χ(G)\chi'(G) (fewest colors for a proper edge coloring). That lecture told us what these quantities are; it did not tell us how to compute anything. This lecture is about doing things with graphs: first the two systematic traversals (breadth- and depth-first search) that visit every vertex, then five classical algorithms that produce colorings, shortest paths, and cheapest connecting networks, together with the data structures that make them fast and the proofs that make them trustworthy.

Three questions organize the chapter, each drawn from a real computational need.

  1. How do we schedule conflicting tasks with as few resources as possible? This is graph coloring. We study greedy vertex coloring and greedy edge coloring — fast, simple heuristics that always produce a valid coloring and come with provable upper bounds on how many colors they use.
  2. What is the cheapest way to get from one place to every other place? This is the single-source shortest-path problem, solved by Dijkstra’s algorithm when edge weights are non-negative.
  3. What is the cheapest network that keeps everything connected? This is the minimum spanning tree problem, solved by Prim’s and Kruskal’s algorithms — two greedy routes to the same optimum, both justified by a single beautiful lemma, the cut property.

A recurring theme: greed

Every optimization algorithm in this lecture is greedy (the two traversals, BFS and DFS, are the exception): it builds its answer incrementally and, at each step, commits to the choice that looks best right now, never reconsidering. Greedy algorithms are attractive because they are simple and fast, but the deep question is always the same:

Does taking the locally best choice at every step lead to a globally optimal result?

The answer here is instructive precisely because it is not uniform:

  • For shortest paths and minimum spanning trees, greedy is exactly optimal, and we will prove it — via the “finalized-distance” invariant for Dijkstra and via the cut property for Prim and Kruskal.
  • For coloring, greedy is only an approximation: it always yields a proper coloring, but may use more colors than the optimum χ(G)\chi(G) or χ(G)\chi'(G), and the number it uses depends on the order in which objects are processed.

Keeping this contrast in mind — when greed is provably safe and when it is merely a bounded heuristic — is one of the main takeaways of the lecture.

Prerequisites and notation

We lean on the following from Lecture 10 (graph basics):

  • A weighted graph attaches a real number w(u,v)w(u,v) — a weight, cost, length, or capacity — to each edge.
  • The adjacency list stores, for each vertex vv, the list of its neighbors together with edge weights. It uses O(n+m)O(n+m) space and lets us scan a vertex’s incident edges in time proportional to its degree. This is the representation assumed throughout. The alternative adjacency matrix costs Θ(n2)\Theta(n^2) space and is only competitive on very dense graphs.
  • A proper coloring never gives the same color to two adjacent objects (adjacent vertices for vertex coloring; edges sharing an endpoint for edge coloring).
  • Δ=Δ(G)\Delta = \Delta(G) denotes the maximum degree of the graph, and deg(v)\deg(v) the degree of vertex vv. Recall the Handshaking Lemma vdeg(v)=2m\sum_v \deg(v) = 2m.

We write n=Vn = |V| for the number of vertices and m=Em = |E| for the number of edges. Inside asymptotic bounds we follow the standard convention (as in CLRS) of writing VV and EE for these counts, so that “O(V+E)O(V+E)” means “O(n+m)O(n+m)”; the letters name the sets, but in an O()O(\cdot) they stand for the sizes. Spanning trees, forests, and the structural theory of trees are the subject of Lecture 12; we borrow two facts from there and flag them where used.

The running graph GG — defined and drawn in the next section — places AA at the far left, FF at the far right, and B,CB,C / D,ED,E as two middle columns, with every edge labelled by its weight. The single figure is reused for all five algorithms: coloring ignores the weights, the path/tree algorithms use them.


11.2 A word on greedy algorithms

Before the graph algorithms, it helps to name the pattern they share.

Greedy algorithm — an algorithm that constructs a solution in a sequence of steps, and at each step irrevocably makes the choice that is locally optimal according to some fixed rule, without backtracking.

Greedy strategy: take the cheapest option at each step

Two ingredients decide whether a greedy algorithm is correct (produces a globally optimal answer), rather than merely fast:

  • The greedy-choice property: there is always a globally optimal solution that is consistent with the greedy first choice. (For MSTs this is exactly the cut property.)
  • Optimal substructure: an optimal solution to the whole problem contains optimal solutions to subproblems, so that after making the greedy choice, solving the rest greedily still yields the optimum.

When both hold, an exchange argument (swap the greedy choice into an arbitrary optimal solution without making it worse) proves optimality. Shortest paths and MSTs enjoy both, which is why Dijkstra, Prim, and Kruskal are exact. Coloring lacks the greedy-choice property — the locally cheapest color can paint us into a corner — so greedy coloring is only a bounded approximation. (The deepest reason MST greed works is that the sets of “edges extendable to a spanning tree” form a matroid; matroids are exactly the structures on which greedy is always optimal. See Further reading.)

Remark. “Greedy” is a strategy, not a guarantee. The same word covers a provably optimal algorithm (Dijkstra) and a heuristic that can be arbitrarily far from optimal (greedy coloring on a bad ordering). Always ask which case you are in.


11.3 The running weighted graph

To let the results be compared, we run Dijkstra, Prim, and Kruskal — and, ignoring the weights, both coloring algorithms — on one shared weighted graph G=(V,E)G=(V,E).

V={A,B,C,D,E,F},V=\{A,B,C,D,E,F\},

with the nine weighted edges

Edge ww Edge ww Edge ww
ABA\text{–}B 4 BDB\text{–}D 5 DED\text{–}E 3
ACA\text{–}C 2 CDC\text{–}D 8 DFD\text{–}F 6
BCB\text{–}C 1 CEC\text{–}E 10 EFE\text{–}F 7

As adjacency lists (neighbors with weights):

A:{B(4),C(2)}B:{A(4),C(1),D(5)}C:{A(2),B(1),D(8),E(10)}D:{B(5),C(8),E(3),F(6)}E:{C(10),D(3),F(7)}F:{D(6),E(7)}\begin{aligned} A&:\{B(4),\,C(2)\} & B&:\{A(4),\,C(1),\,D(5)\} & C&:\{A(2),\,B(1),\,D(8),\,E(10)\}\\ D&:\{B(5),\,C(8),\,E(3),\,F(6)\} & E&:\{C(10),\,D(3),\,F(7)\} & F&:\{D(6),\,E(7)\} \end{aligned}

The running weighted graph G on six vertices with its nine labelled edge weights

Degrees are deg(A)=2, deg(B)=3, deg(C)=4, deg(D)=4, deg(E)=3, deg(F)=2\deg(A)=2,\ \deg(B)=3,\ \deg(C)=4,\ \deg(D)=4,\ \deg(E)=3,\ \deg(F)=2, so the maximum degree is Δ=4\Delta=4 and (Handshaking check) vdeg(v)=18=29=2m\sum_v\deg(v)=18=2\cdot 9=2m. The graph is connected (needed for the MST algorithms) and its nine weights are distinct (which, as we prove later, makes the MST unique — so Prim and Kruskal must return the very same edge set, an ideal setup for comparison). It also contains several triangles (ABCABC, BCDBCD, CDECDE, DEFDEF), which will force at least three colors.


Before optimizing over a graph we must be able to visit it systematically — reach every vertex, discover the connected component of a start vertex, and build a tree of first-discoveries. The two canonical traversals differ only in the data structure that holds the frontier of discovered-but-unexplored vertices: a queue gives breadth-first search (BFS), a stack (or recursion) gives depth-first search (DFS).

BFS(G, s):                              DFS(G, s):          # recursive
    mark every vertex unvisited             mark s visited
    Q ← queue holding s;  visit s           for each neighbor w of s:
    while Q not empty:                          if w unvisited:
        u ← dequeue(Q)                              DFS(G, w)
        for each neighbor w of u:
            if w unvisited:
                visit w;  enqueue(Q, w)

DFS is usually written recursively — the call stack is the stack; an explicit-stack variant simply replaces BFS’s queue by a stack. Each vertex is enqueued once and each edge examined a constant number of times, so with adjacency lists both run in O(V+E)O(|V|+|E|)linear in the size of the graph.

Worked trace on GG (weights ignored), starting at AA, neighbors in alphabetical order.

  • BFS visits A; B,C; D,E; FA;\ B,C;\ D,E;\ F in rings of increasing distance from AA. Its tree of discovery edges is {AB,AC,BD,CE,DF}\{AB,AC,BD,CE,DF\}, and the ring number is exactly the fewest-edges distance from AA: A=0, B=1, C=1, D=2, E=2, F=3A{=}0,\ B{=}1,\ C{=}1,\ D{=}2,\ E{=}2,\ F{=}3.
  • DFS plunges ABCDEFA\to B\to C\to D\to E\to F, backtracking only at dead ends; its discovery tree is the path {AB,BC,CD,DE,EF}\{AB,BC,CD,DE,EF\}.

Same vertices, different trees — BFS stays shallow and wide, DFS goes deep.

What traversal is for.

  • Connectivity and components. One traversal reaches exactly the start vertex’s component; restarting from any still-unvisited vertex counts the connected components (§10.9) in O(V+E)O(|V|+|E|).
  • Shortest paths in an unweighted graph. BFS’s ring number is the minimum number of edges from the source — the unweighted counterpart of Dijkstra below, and far cheaper when the weights are all equal.
  • Paths and cycles. DFS underlies path-finding, cycle detection, and topological ordering; the findPath/findCircuit routines of Lab 6 are DFS at heart.

Traversal is the workhorse the rest of this chapter leans on: Dijkstra’s and Prim’s frontier-growing loops are BFS with a priority queue in place of a plain one.


11.5 Greedy vertex coloring

Motivation

Vertex coloring is the abstract form of conflict scheduling. Model each task as a vertex and join two tasks by an edge when they conflict (cannot share a resource). A proper coloring is then an assignment of resources — exam time slots, radio frequencies, CPU registers — such that no two conflicting tasks collide, and the number of colors is the number of resources needed. The classic compiler application is register allocation: variables are vertices, an edge means two variables are “live” simultaneously, colors are machine registers.

Finding χ(G)\chi(G) exactly is NP-hard, so in practice we reach for a fast heuristic. The simplest is greedy.

The algorithm

Greedy vertex coloring — fix an ordering v1,v2,,vnv_1, v_2, \dots, v_n of the vertices; visit them in that order and give each vertex the smallest color (a positive integer 1,2,3,1, 2, 3, \dots) not already used by any of its already-colored neighbors.

function GreedyVertexColoring(G, ordering v1..vn):
    for i = 1 to n:
        used ← { color[u] : u is a neighbor of vi and u already colored }
        color[vi] ← smallest positive integer not in used
    return color

Finding the smallest free color efficiently. Naively “scanning for a gap” sounds like it could be slow, but it is not. Keep a scratch boolean array seen[1..Δ+1]. When coloring viv_i, for each already-colored neighbor uu with color[u]deg(vi)+1\text{color}[u]\le \deg(v_i)+1 set seen[color[u]] ← true; then scan c=1,2,c = 1,2,\dots for the first index with seen[c] = false. Because viv_i has at most deg(vi)\deg(v_i) colored neighbors, the answer is at most deg(vi)+1\deg(v_i)+1, so the scan stops within deg(vi)+1\deg(v_i)+1 steps. Reset only the entries you touched (or use a version-stamp trick) so the per-vertex cost stays O(deg(vi))O(\deg(v_i)).

Worked trace

Run greedy on GG (ignoring weights) in the natural order A,B,C,D,E,FA,B,C,D,E,F. The “colored neighbors” column lists each neighbor that is already colored and its color.

Step Vertex Colored neighbors (colors) Forbidden colors Assigned
1 AA {}\{\} 1
2 BB A(1)A(1) {1}\{1\} 2
3 CC A(1),B(2)A(1),\,B(2) {1,2}\{1,2\} 3
4 DD B(2),C(3)B(2),\,C(3) {2,3}\{2,3\} 1
5 EE C(3),D(1)C(3),\,D(1) {1,3}\{1,3\} 2
6 FF D(1),E(2)D(1),\,E(2) {1,2}\{1,2\} 3

Colors used: {1,2,3}\{1,2,3\} — three colors. The final coloring is A=1, B=2, C=3, D=1, E=2, F=3A{=}1,\ B{=}2,\ C{=}3,\ D{=}1,\ E{=}2,\ F{=}3; a quick check of all nine edges confirms every edge joins two differently colored endpoints, so it is proper.

Is three optimal? Yes: the triangle ABCA\text{–}B\text{–}C has three mutually adjacent vertices, all of which need distinct colors, so χ(G)3\chi(G)\ge 3; and greedy exhibited a proper 33-coloring, so χ(G)=3\chi(G)=3 and here greedy is optimal.

Correctness: greedy always produces a proper coloring

Theorem 11.1. For every graph GG and every vertex ordering, GreedyVertexColoring returns a proper coloring.

Proof. Take any edge uvEuv \in E; we must show color[u]color[v]\text{color}[u]\ne\text{color}[v]. Without loss of generality uu is colored before vv (one of them is). At the moment vv is colored, uu is an already-colored neighbor of vv, so color[u]\text{color}[u] lies in the set used that vv avoids. Hence color[v]color[u]\text{color}[v]\ne\text{color}[u]. As uvuv was arbitrary, no edge is monochromatic. \square

Correctness: the Δ+1\Delta+1 bound

Theorem 11.2. GreedyVertexColoring uses at most Δ+1\Delta+1 colors, for every ordering. Consequently χ(G)Δ+1\chi(G)\le \Delta+1.

Proof. When vertex viv_i is colored, the forbidden set used is contained in the set of colors of viv_i’s already-colored neighbors, so useddeg(vi)Δ|\texttt{used}| \le \deg(v_i)\le\Delta. Among the Δ+1\Delta+1 colors {1,2,,Δ+1}\{1,2,\dots,\Delta+1\} at most Δ\Delta are forbidden, so at least one is free; greedy picks the smallest free color, which is therefore Δ+1\le\Delta+1. No vertex ever receives a color exceeding Δ+1\Delta+1, so the total number of colors used is Δ+1\le\Delta+1. Since the coloring is proper (Theorem 11.1), χ(G)Δ+1\chi(G)\le\Delta+1. \square

On GG, Δ=4\Delta=4, so the theorem promises 5\le 5 colors; greedy did much better with 33 because the ordering was favorable.

Remark (Brooks’ theorem). The bound Δ+1\Delta+1 is rarely tight. Brooks’ theorem (1941) states that a connected graph satisfies χ(G)Δ\chi(G)\le\Delta unless GG is a complete graph KnK_n (which needs n=Δ+1n=\Delta+1) or an odd cycle (which needs 3=Δ+13=\Delta+1). Our GG is neither, and indeed χ(G)=3Δ=4\chi(G)=3\le\Delta=4.

The ordering matters

Theorem 11.2 bounds the worst case, but the actual number of colors depends heavily on the processing order. Greedy is always proper, yet the color count is not.

A concrete gap on GG. Run greedy on the same graph GG in the order A,E,B,D,C,FA,E,B,D,C,F:

Step Vertex Colored neighbors (colors) Assigned
1 AA 1
2 EE — (none of C,D,FC,D,F colored yet) 1
3 BB A(1)A(1) 2
4 DD B(2),E(1)B(2),\,E(1) 3
5 CC A(1),B(2),D(3),E(1)A(1),\,B(2),\,D(3),\,E(1) 4
6 FF D(3),E(1)D(3),\,E(1) 2

This uses four colors, although χ(G)=3\chi(G)=3. The culprit is vertex CC: by the time it is reached, its four neighbors already carry three distinct colors {1,2,3}\{1,2,3\}, forcing a fourth. Same graph, same algorithm, different order — one extra color.

A dramatic gap: the crown graph. The crown graph Sn0S_n^0 is Kn,nK_{n,n} with a perfect matching deleted: parts {x0,,xn1}\{x_0,\dots,x_{n-1}\} and {y0,,yn1}\{y_0,\dots,y_{n-1}\}, with xix_i adjacent to every yjy_j except yiy_i. It is bipartite, so χ=2\chi=2. But process the vertices in the paired order x0,y0,x1,y1,,xn1,yn1x_0,y_0,x_1,y_1,\dots,x_{n-1},y_{n-1}: an easy induction shows xkx_k and yky_k each see exactly the colors {1,,k}\{1,\dots,k\} on already-colored neighbors and so receive color k+1k+1. Greedy therefore uses nn colors on 2n2n vertices — arbitrarily far from the true value 22. (For n=3n=3 this already forces 33 colors instead of 22.) There is always an ordering achieving the optimum χ(G)\chi(G) — color the vertices in the order an optimal coloring would — but finding such an order is as hard as coloring itself.

The Welsh–Powell heuristic

Since order is everything, a good default is to color high-degree vertices first, on the intuition that they are the hardest to satisfy and should be handled while colors are plentiful.

Welsh–Powell coloring — sort the vertices in non-increasing order of degree, then run greedy in that order.

function WelshPowell(G):
    sort vertices so that deg(v1) ≥ deg(v2) ≥ … ≥ deg(vn)
    return GreedyVertexColoring(G, v1..vn)

On GG the degrees are C:4, D:4, B:3, E:3, A:2, F:2C{:}4,\ D{:}4,\ B{:}3,\ E{:}3,\ A{:}2,\ F{:}2, giving the order C,D,B,E,A,FC,D,B,E,A,F:

Step Vertex Colored neighbors (colors) Assigned
1 CC 1
2 DD C(1)C(1) 2
3 BB C(1),D(2)C(1),\,D(2) 3
4 EE C(1),D(2)C(1),\,D(2) 3
5 AA B(3),C(1)B(3),\,C(1) 2
6 FF D(2),E(3)D(2),\,E(3) 1

Three colors — optimal. Welsh–Powell also yields the useful bound χ(G)maximin{di+1, i}\chi(G)\le \max_i \min\{\,d_i+1,\ i\,\}, where d1d2d_1\ge d_2\ge\cdots is the sorted degree sequence and ii is the position. It is a heuristic, not a guarantee: there exist graphs where even largest-degree-first is suboptimal. But it is cheap and usually good.

Complexity and data structures

Data structure: adjacency lists plus one reusable scratch array seen[1..Δ+1].

Time: for each vertex viv_i we do O(deg(vi))O(\deg(v_i)) work (mark neighbor colors, scan for the first gap, reset). Summing over all vertices and using the Handshaking Lemma,

i=1nO(deg(vi))=O ⁣(n+vdeg(v))=O(n+2m)=O(V+E).\sum_{i=1}^{n} O(\deg(v_i)) = O\!\Big(n + \sum_v \deg(v)\Big) = O(n + 2m) = O(V+E).

Welsh–Powell adds an O(VlogV)O(V\log V) sort, giving O(VlogV+E)O(V\log V + E). Space is O(V)O(V) beyond the graph itself.

Common pitfall. Do not believe that greedy computes χ(G)\chi(G). It computes a proper coloring whose size is between χ(G)\chi(G) and Δ+1\Delta+1, and which depends on the ordering. If you need the true chromatic number you must search (exponential in the worst case).

CS connection. Chaitin’s classic register allocator (1981) models allocation as vertex coloring of an interference graph and colors it greedily; when more than kk colors (= registers) are needed, some variable is “spilled” to memory — the practical response to greedy’s non-optimality.


11.6 Greedy edge coloring

Motivation

Edge coloring is the natural model for timetabling round-robin schedules. Represent players (or teams, machines, people) as vertices and each required match/meeting as an edge; a color is a time slot. Two edges sharing an endpoint must get different colors (a player cannot play two matches at once), so a proper edge coloring is a conflict-free schedule and χ(G)\chi'(G) is the fewest time slots needed.

The algorithm

Greedy edge coloring — fix an ordering of the edges; give each edge the smallest color not used by any adjacent edge (an edge that shares an endpoint) that is already colored.

function GreedyEdgeColoring(G, ordering e1..em):
    for k = 1 to m:
        let ek = (u, v)
        used ← { color[e] : e already colored and e shares endpoint u or v with ek }
        color[ek] ← smallest positive integer not in used
    return color

This is literally greedy vertex coloring run on the line graph L(G)L(G), whose vertices are the edges of GG and whose edges join pairs of GG-edges that share an endpoint. Every theorem about greedy vertex coloring transfers, with Δ(L(G))\Delta(L(G)) in place of Δ(G)\Delta(G).

Worked trace

Color GG processing edges in the order AB,AC,BC,BD,CD,CE,DE,DF,EFAB, AC, BC, BD, CD, CE, DE, DF, EF. Recall Δ(G)=4\Delta(G)=4.

Step Edge Adjacent colored edges (colors) Assigned
1 ABAB 1
2 ACAC AB(1)AB(1) 2
3 BCBC AB(1),AC(2)AB(1),\,AC(2) 3
4 BDBD AB(1),BC(3)AB(1),\,BC(3) 2
5 CDCD AC(2),BC(3),BD(2)AC(2),\,BC(3),\,BD(2) 1
6 CECE AC(2),BC(3),CD(1)AC(2),\,BC(3),\,CD(1) 4
7 DEDE BD(2),CD(1),CE(4)BD(2),\,CD(1),\,CE(4) 3
8 DFDF BD(2),CD(1),DE(3)BD(2),\,CD(1),\,DE(3) 4
9 EFEF CE(4),DE(3),DF(4)CE(4),\,DE(3),\,DF(4) 1

Four colors. The four color classes are

1:{AB,CD,EF}2:{AC,BD}3:{BC,DE}4:{CE,DF},1:\{AB,CD,EF\}\quad 2:\{AC,BD\}\quad 3:\{BC,DE\}\quad 4:\{CE,DF\},

and each is a matching (no two of its edges share a vertex; the formal treatment and Hall’s theorem are in Lecture 12 §12.9) — which is exactly what “proper edge coloring” means. Class 11 is even a perfect matching, pairing up all six vertices. Since χ(G)Δ=4\chi'(G)\ge\Delta=4 (the four edges at CC must all differ), greedy’s four colors are optimal here: χ(G)=4\chi'(G)=4.

Correctness and bounds

Theorem 11.3 (proper). GreedyEdgeColoring always returns a proper edge coloring. Proof. It is Theorem 11.1 applied to the line graph L(G)L(G): two edges sharing an endpoint are adjacent vertices of L(G)L(G), and the algorithm gives them different colors by the same argument. \square

Theorem 11.4 (the 2Δ12\Delta-1 bound). GreedyEdgeColoring uses at most 2Δ12\Delta-1 colors. Proof. An edge e=uve=uv is adjacent to at most (degu1)+(degv1)2Δ2(\deg u-1)+(\deg v-1)\le 2\Delta-2 other edges, hence to at most 2Δ22\Delta-2 already-colored ones. Among the colors {1,,2Δ1}\{1,\dots,2\Delta-1\} at least one is free, and greedy takes the smallest such. \square

How good is 2Δ12\Delta-1? Compare it with the optimum, pinned down by a famous theorem from Lecture 10.

Vizing’s theorem. Every simple graph satisfies χ(G){Δ, Δ+1}\chi'(G)\in\{\Delta,\ \Delta+1\}.

So the true optimum is one of just two values, whereas greedy only promises 2Δ12\Delta-1 — off by nearly a factor of two in the worst case. On GG greedy happened to hit the optimum Δ=4\Delta=4, but in general greedy edge coloring is a weaker approximation than the theory allows.

Complexity

Data structure: adjacency lists plus a scratch array of size 2Δ2\Delta.

Time: for edge e=uve=uv we scan its 2Δ2\le 2\Delta-2 adjacent edges and find the smallest free color in O(Δ)O(\Delta). Summed over all edges,

uvE(degu+degv)=vdeg(v)2Δvdeg(v)=2Δm,\sum_{uv\in E}\big(\deg u+\deg v\big) = \sum_v \deg(v)^2 \le \Delta\sum_v\deg(v)=2\Delta m,

so the total is O(EΔ)O(E\cdot\Delta). Space O(V+E)O(V+E).

Common pitfall. “Adjacent” for edge coloring means sharing an endpoint, not being consecutive in the input list. An edge must avoid the colors of edges at both of its endpoints.


11.7 Dijkstra’s shortest-path algorithm

Motivation and the problem

Given a weighted graph and a start vertex, we want the cheapest route to everywhere else: the single-source shortest-path problem. This is the computational core of GPS navigation, packet routing (the link-state protocol OSPF runs Dijkstra), network latency analysis, and countless planning tasks.

Shortest-path distance — write δ(s,v)\delta(s,v) for the minimum weight of any path from the source ss to vv (and δ(s,v)=\delta(s,v)=\infty if vv is unreachable). A shortest path is a path achieving this minimum.

Dijkstra’s algorithm finds δ(s,v)\delta(s,v) for every vv in a graph whose edge weights are all non-negative. It maintains a tentative distance dist[v]\text{dist}[v] for each vertex — the length of the best route to vv found so far — repeatedly finalizes the unfinished vertex of smallest tentative distance, and then relaxes that vertex’s edges.

Relaxation — for an edge (u,v)(u,v): if dist[u]+w(u,v)<dist[v]\text{dist}[u] + w(u,v) < \text{dist}[v], a shorter route to vv through uu has been found, so update dist[v]dist[u]+w(u,v)\text{dist}[v] \leftarrow \text{dist}[u] + w(u,v) and set prev[v]u\text{prev}[v]\leftarrow u (recording the predecessor, so paths can be reconstructed).

Pseudocode

function Dijkstra(G, w, s):
    for each vertex v in G:
        dist[v] ← ∞
        prev[v] ← undefined
    dist[s] ← 0
    Q ← min-priority queue of all vertices, keyed by dist
    while Q is not empty:
        u ← extract-min(Q)                 # closest not-yet-finalized vertex
        for each neighbor v of u:
            alt ← dist[u] + w(u, v)
            if alt < dist[v]:              # relaxation
                dist[v] ← alt
                prev[v] ← u
                decrease-key(Q, v, alt)
    return dist, prev

Once a vertex is extracted from QQ, its distance is final and never changes again — the crux of the correctness proof below.

Worked trace

Run Dijkstra on GG from source AA. Each row shows the tentative-distance array after one more vertex has been finalized; the Chosen column names the vertex just extracted (with its now-final distance), and a dash “-” means “not yet reached” (\infty).

Step Chosen AA BB CC DD EE FF
0 (init) 0 - - - - -
1 A(0)A\,(0) 0 4 2 - - -
2 C(2)C\,(2) 0 3 2 10 12 -
3 B(3)B\,(3) 0 3 2 8 12 -
4 D(8)D\,(8) 0 3 2 8 11 14
5 E(11)E\,(11) 0 3 2 8 11 14
6 F(14)F\,(14) 0 3 2 8 11 14

Reading the steps (bold entries mark a strict improvement by relaxation):

  • Step 1 — extract AA (dist 00); relax its edges, giving B=4B=4 and C=2C=2.
  • Step 2 — the smallest tentative distance is C=2C=2; finalize it and relax CC’s edges. Edge CBC\text{–}B gives 2+1=3<42+1=3<4, so BB improves from 44 to 33 (the route ACBA\to C\to B beats the direct edge ABA\to B). Also D=2+8=10D=2+8=10 and E=2+10=12E=2+10=12.
  • Step 3 — extract B=3B=3; relax BDB\text{–}D: 3+5=8<103+5=8<10, so DD improves to 88 (route ACBDA\to C\to B\to D). (BB’s only neighbors are A,C,DA,C,D, so EE is untouched here.)
  • Step 4 — extract D=8D=8; relax DED\text{–}E: 8+3=11<128+3=11<12, so EE improves to 1111; relax DFD\text{–}F: F=8+6=14F=8+6=14.
  • Step 5 — extract E=11E=11; relax EFE\text{–}F: 11+7=18>1411+7=18 > 14, no improvement — the longer route is correctly rejected.
  • Step 6 — extract F=14F=14; nothing left to improve. Done.

Final distances from AA: A=0, B=3, C=2, D=8, E=11, F=14A{=}0,\ B{=}3,\ C{=}2,\ D{=}8,\ E{=}11,\ F{=}14. Three separate vertices (BB, DD, EE) had their distances improved after first being reached — the essence of relaxation — and FF’s tempting-but-worse route through EE was discarded.

Path reconstruction. Follow prev\text{prev} backward from the target. For FF: prev[F]=D, prev[D]=B, prev[B]=C, prev[C]=A\text{prev}[F]=D,\ \text{prev}[D]=B,\ \text{prev}[B]=C,\ \text{prev}[C]=A, giving the shortest path ACBDFA\to C\to B\to D\to F of length 2+1+5+6=142+1+5+6=14. The prev\text{prev} pointers of all vertices together form the shortest-path tree rooted at AA.

Shortest-path tree from A (highlighted) on the running graph; farthest vertex F at distance 14

Correctness

Two elementary facts first, then the main theorem.

Lemma A (upper-bound property). At all times dist[v]δ(s,v)\text{dist}[v]\ge\delta(s,v), and once dist[v]\text{dist}[v] is finite it is the length of some actual ssvv path. Proof. Initially dist[s]=0=δ(s,s)\text{dist}[s]=0=\delta(s,s) and all others are \infty. Relaxation only ever sets dist[v]=dist[u]+w(u,v)\text{dist}[v]=\text{dist}[u]+w(u,v), which is the length of the path to uu (a real path, inductively) extended by edge (u,v)(u,v); and no real path can be shorter than the minimum δ(s,v)\delta(s,v). \square

Lemma B (subpath optimality). If s=x0,x1,,xks=x_0,x_1,\dots,x_k is a shortest path, then each prefix x0,,xjx_0,\dots,x_j is a shortest path to xjx_j, so δ(s,xj)+w(xj,xj+1)=δ(s,xj+1)\delta(s,x_j)+w(x_j,x_{j+1})= \delta(s,x_{j+1}). Proof. A cheaper route to a prefix endpoint could be spliced in to beat the whole path, contradicting its optimality. \square

Theorem 11.5 (Dijkstra correctness). When Dijkstra extracts a vertex uu from QQ, its distance is correct: dist[u]=δ(s,u)\text{dist}[u]=\delta(s,u). Hence all final distances are correct.

Proof (by contradiction on the extraction order). Suppose not, and let uu be the first vertex extracted with dist[u]δ(s,u)\text{dist}[u]\ne\delta(s,u). By Lemma A, dist[u]δ(s,u)\text{dist}[u]\ge \delta(s,u), so in fact dist[u]>δ(s,u)\text{dist}[u]>\delta(s,u). Note usu\ne s (since dist[s]=0=δ(s,s)\text{dist}[s]=0 =\delta(s,s) and ss is extracted first, correctly), and δ(s,u)<\delta(s,u)<\infty (otherwise dist[u]==δ(s,u)\text{dist}[u]=\infty=\delta(s,u)), so a shortest path PP from ss to uu exists.

Let SS be the set of vertices already finalized (extracted) just before uu is extracted. Then sSs\in S but uSu\notin S, so PP starts inside SS and ends outside it. Walk along PP and let yy be the first vertex on PP that is not in SS, and let xx be its predecessor on PP (so xSx\in S; possibly x=sx=s).

Claim: dist[y]=δ(s,y)\text{dist}[y]=\delta(s,y). Since xSx\in S, xx was extracted before uu, and by our choice of uu as the first bad vertex, dist[x]=δ(s,x)\text{dist}[x]=\delta(s,x) held at xx’s extraction. At that moment we relaxed edge (x,y)(x,y), so afterward dist[y]dist[x]+w(x,y)=δ(s,x)+w(x,y)\text{dist}[y]\le\text{dist}[x]+w(x,y)=\delta(s,x)+w(x,y). By Lemma B (as x,yx,y are consecutive on the shortest path PP), δ(s,x)+w(x,y)=δ(s,y)\delta(s,x)+w(x,y)=\delta(s,y). Thus dist[y]δ(s,y)\text{dist}[y]\le\delta(s,y), and with Lemma A, dist[y]=δ(s,y)\text{dist}[y]=\delta(s,y), proving the claim.

Now yy lies on PP at or before uu, and the piece of PP from yy to uu has non-negative total weight (this is the only place non-negativity is used!), so δ(s,y)δ(s,u)\delta(s,y)\le\delta(s,u). Chaining the (in)equalities,

dist[y]=δ(s,y)δ(s,u)<dist[u].\text{dist}[y]=\delta(s,y)\le\delta(s,u)<\text{dist}[u].

But both yy and uu are in QQ when the algorithm chose to extract uu by minimum tentative distance, forcing dist[u]dist[y]\text{dist}[u]\le\text{dist}[y] — contradicting dist[y]<dist[u]\text{dist}[y]<\text{dist}[u]. No such uu exists, so every extraction is correct. \square

Why non-negativity is essential. The single step δ(s,y)δ(s,u)\delta(s,y)\le\delta(s,u) assumes the yuy\to u portion of PP costs 0\ge 0. A negative edge breaks this, and Dijkstra can finalize a vertex too early.

Common pitfall — negative weights. Consider the directed graph ABA\to B (w=2w=2), ACA\to C (w=3w=3), CBC\to B (w=2w=-2). Dijkstra extracts AA, sets dist[B]=2, dist[C]=3\text{dist}[B]=2,\ \text{dist}[C]=3, then extracts and finalizes BB at 22 (it is the smallest), before ever examining CC. But the true shortest route is ACBA\to C\to B with length 3+(2)=13+(-2)=1. Dijkstra never revisits a finalized vertex, so it reports dist[B]=2\text{dist}[B]=2wrong. (If BB had any outgoing edge, the premature value 22 would propagate, corrupting descendants too.) For graphs with negative edge weights use the Bellman–Ford algorithm, which relaxes every edge V1V-1 times in O(VE)O(VE) and, on a VV-th pass that still improves something, detects a negative-weight cycle (on which no shortest path exists).

Complexity

The running time depends entirely on the priority queue. Let TexT_{\text{ex}} and TdkT_{\text{dk}} be the costs of extract-min and decrease-key. There are VV extractions and at most EE decrease-keys (one per relaxation), giving O(VTex+ETdk)O(V\cdot T_{\text{ex}} + E\cdot T_{\text{dk}}).

Priority queue extract-min decrease-key Total Best for
Binary (min-)heap O(logV)O(\log V) O(logV)O(\log V) O((V+E)logV)=O(ElogV)O((V+E)\log V)=O(E\log V) general/sparse
Unsorted array O(V)O(V) O(1)O(1) O(V2+E)=O(V2)O(V^2+E)=O(V^2) dense (E ⁣ ⁣V2E\!\approx\!V^2)
Fibonacci heap O(logV)O(\log V)* O(1)O(1)* O(E+VlogV)O(E+V\log V) theory / very large sparse

(*amortized.) The workhorse is the binary heap, an array-embedded complete binary tree in which every node’s key is \le its children’s; insert, decrease-key, and extract-min each cost O(logV)O(\log V) by sifting an element up or down one tree level at a time. For a connected graph EV1E\ge V-1, so O((V+E)logV)=O(ElogV)O((V+E)\log V)=O(E\log V).

Remark (lazy deletion). Many implementations skip decrease-key: on each relaxation they simply push a new (dist,v)(\text{dist},v) pair and, on extraction, discard any pair whose key exceeds the current dist[v]\text{dist}[v]. The heap may hold up to EE entries, but since logElogV2=2logV\log E\le\log V^2=2\log V, the bound is still O(ElogV)O(E\log V). This trades a little memory for much simpler code and is what most standard libraries do.

Common pitfall. Do not “update in place then forget to re-heapify.” Either call a real decrease-key, or use lazy deletion and check for stale entries at extraction — otherwise the queue’s min is wrong.

CS connection. Dijkstra is the basis of link-state routing (OSPF, IS-IS) and of the “uniform-cost search” in AI; adding a goal-directed heuristic turns it into the A* search algorithm used in games and mapping.


11.8 Minimum spanning trees and the cut property

The problem

Anticipating Lecture 12: a spanning tree of a connected graph GG is a subgraph that is a tree (connected, acyclic) and includes every vertex; it necessarily has exactly n1n-1 edges. A minimum spanning tree (MST) is a spanning tree of least total edge weight. MSTs answer “what is the cheapest set of links that keeps the whole network connected?” — laying cable, roads, or pipelines; they also seed clustering algorithms and give a 22-approximation for the metric traveling-salesman problem.

Both algorithms that follow are greedy, and both are proved correct by a single lemma. To state it we need the vocabulary of cuts.

Cut. A cut (S, VS)(S,\ V\setminus S) is a partition of the vertices into two nonempty parts. Crossing edge. An edge crosses the cut if it has one endpoint in SS and the other in VSV\setminus S. Light edge. A crossing edge of minimum weight is a light edge of the cut. Respecting a cut. A set of edges AA respects the cut if no edge of AA crosses it. Safe edge. An edge ee is safe for AA if AA\subseteq (some MST) implies A{e}A\cup\{e\}\subseteq (some MST).

The cut property

Theorem 11.6 (Cut Property). Let AA be a set of edges contained in some MST of GG. Let (S,VS)(S,V\setminus S) be any cut that AA respects, and let ee be a light edge crossing this cut. Then ee is safe for AA.

Proof (exchange argument). Let TT be an MST with ATA\subseteq T. If eTe\in T we are done, since then A{e}TA\cup\{e\}\subseteq T. So suppose e=(u,v)Te=(u,v)\notin T, with uSu\in S and vVSv\in V\setminus S.

Adding ee to the tree TT creates exactly one cycle C\mathcal C (a fundamental fact about trees, Lecture 12: any two vertices already have a unique path in TT, and ee closes it into a cycle). Traversing C\mathcal C, we start at uSu\in S, use ee to reach vVSv\in V\setminus S, and eventually return to uu; since each crossing switches sides and we must end where we began, C\mathcal C crosses the cut an even number of times. It crosses once at ee, so it crosses at least once more, at some edge eee'\ne e.

This ee' is not in AA: it crosses the cut, but AA respects the cut (no AA-edge crosses). Now exchange: set T=(T{e}){e}T' = (T\setminus\{e'\})\cup\{e\}. Removing ee', an edge on the cycle C\mathcal C, keeps TT' connected; it still has n1n-1 edges, so TT' is a spanning tree. Its weight is

w(T)=w(T)w(e)+w(e).w(T') = w(T) - w(e') + w(e).

Because ee is a light (minimum-weight) crossing edge and ee' also crosses, w(e)w(e)w(e)\le w(e'), so w(T)w(T)w(T')\le w(T). But TT is an MST, so w(T)w(T)w(T')\ge w(T); hence w(T)=w(T)w(T')=w(T) and TT' is also an MST. Finally ATA\subseteq T' (we removed eAe'\notin A and added ee) and eTe\in T', so A{e}TA\cup\{e\}\subseteq T': the edge ee is safe. \square

Theorem 11.7 (Cycle Property). For any cycle C\mathcal C in GG, if ee is a strict-maximum-weight edge of C\mathcal C, then ee belongs to no MST. Proof. If some MST TT contained ee, deleting ee splits TT into two components; the rest of the cycle C{e}\mathcal C\setminus\{e\} is a path joining ee’s endpoints, so it contains some edge ee' crossing that split with w(e)<w(e)w(e')<w(e). Then (T{e}){e}(T\setminus\{e\})\cup\{e'\} is a spanning tree of smaller weight — contradiction. \square

The cut property tells us which edges to include; the cycle property tells us which to exclude. Prim and Kruskal are the two ways to apply the first; Kruskal also uses the second to justify skipping edges.

Corollary 11.8 (uniqueness). If all edge weights are distinct, the MST is unique. Proof. In Theorem 11.6 with distinct weights, a light edge eTe\notin T would give w(e)<w(e)w(e)<w(e') strictly, hence w(T)<w(T)w(T')<w(T), contradicting that TT is an MST — so every MST contains every light edge of every cut, pinning down one tree. \square

Because our running graph GG has distinct weights, its MST is unique, and Prim and Kruskal must produce identical edge sets, not merely equal weights.

The unique minimum spanning tree of the running graph (highlighted), total weight 17


11.9 Prim’s algorithm

Idea and pseudocode

Prim’s algorithm grows a single tree outward from a start vertex. It keeps the set of tree vertices and, at every step, adds the cheapest edge crossing from the tree to a vertex not yet in it. Each vertex vv outside the tree stores key[v]\text{key}[v], the weight of the cheapest known edge connecting it to the current tree, and a priority queue delivers the global minimum.

function Prim(G, w, s):
    for each vertex v:
        key[v] ← ∞          # cheapest known edge connecting v to the tree
        parent[v] ← undefined
        inMST[v] ← false
    key[s] ← 0
    Q ← min-priority queue of all vertices, keyed by key
    while Q is not empty:
        u ← extract-min(Q)
        inMST[u] ← true
        for each neighbor v of u:
            if not inMST[v] and w(u, v) < key[v]:
                key[v] ← w(u, v)
                parent[v] ← u
                decrease-key(Q, v, key[v])
    return { (v, parent[v]) : v ≠ s }      # the MST edges

Worked trace

Run Prim on GG from AA. Each row shows the key[]\text{key}[\,] array after one extraction; “\ast” marks a vertex now in the tree, and bold marks a key just improved. The Added edge is (parent[u],u)(\text{parent}[u],u) for the extracted uu.

Step Extracted Added edge AA BB CC DD EE FF
0 00 \infty \infty \infty \infty \infty
1 AA \ast 4 2 \infty \infty \infty
2 C(2)C\,(2) ACA\text{–}C \ast 1 \ast 8 10 \infty
3 B(1)B\,(1) CBC\text{–}B \ast \ast \ast 5 1010 \infty
4 D(5)D\,(5) BDB\text{–}D \ast \ast \ast \ast 3 6
5 E(3)E\,(3) DED\text{–}E \ast \ast \ast \ast \ast 66
6 F(6)F\,(6) DFD\text{–}F \ast \ast \ast \ast \ast \ast

Narration:

  • Step 1 — from AA, keys of B,CB,C become 4,24,2.
  • Step 2 — extract CC (key 22, cheapest), add ACA\text{–}C. Relaxing CC’s edges drops key[B]\text{key}[B] from 44 to 11 (edge BCB\text{–}C), and sets key[D]=8, key[E]=10\text{key}[D]=8,\ \text{key}[E]=10.
  • Step 3 — extract BB (key 11), add CBC\text{–}B. Edge BDB\text{–}D drops key[D]\text{key}[D] from 88 to 55.
  • Step 4 — extract DD (key 55), add BDB\text{–}D. Edge DED\text{–}E drops key[E]\text{key}[E] from 1010 to 33, and key[F]=6\text{key}[F]=6.
  • Step 5 — extract EE (key 33), add DED\text{–}E. Edge EFE\text{–}F has weight 7>67>6, so key[F]\text{key}[F] is unchanged.
  • Step 6 — extract FF (key 66), add DFD\text{–}F. Tree complete.

MST edges: {AC, BC, BD, DE, DF}\{A\text{–}C,\ B\text{–}C,\ B\text{–}D,\ D\text{–}E,\ D\text{–}F\}; total weight 2+1+5+3+6=172+1+5+3+6 = \mathbf{17}. Five edges for six vertices — a valid spanning tree. Notice Prim added the weights in the order 2,1,5,3,62,1,5,3,6: it takes the cheapest frontier edge, which is generally not the globally cheapest remaining edge (contrast Kruskal).

Correctness

Theorem 11.9. Prim’s algorithm returns a minimum spanning tree.

Proof. Let AA be the set of edges chosen so far, and SS the set of vertices in the tree (those with inMST=true\text{inMST}=\text{true}). We prove the invariant AA is contained in some MST by induction on the steps.

Base. Initially A=TA=\varnothing\subseteq T for any MST TT.

Step. Assume AA\subseteq some MST. Consider the cut (S,VS)(S,V\setminus S). Every edge of AA joins two vertices that entered the tree, hence lies inside SS, so AA respects this cut. The next edge Prim adds is, by construction, the minimum-weight edge from SS to VSV\setminus S — a light edge of the cut. By the Cut Property (Theorem 11.6) this edge is safe, so A{e}A\cup\{e\} is contained in some MST. The invariant is maintained.

When QQ empties, S=VS=V and AA has n1n-1 edges forming a spanning tree contained in an MST; a spanning tree contained in an MST is that MST. \square

(The algorithm never gets stuck while SVS\ne V: because GG is connected there is always at least one edge crossing the cut, so some vertex has finite key.)

Complexity

Structurally Prim is Dijkstra with a different key (“cheapest edge into the tree” instead of “distance from source”), so it has the identical cost profile:

  • Binary heap: O((V+E)logV)=O(ElogV)O((V+E)\log V) = O(E\log V).
  • Unsorted array: O(V2)O(V^2)preferred for dense graphs, since it is independent of EE.
  • Fibonacci heap: O(E+VlogV)O(E + V\log V).

Common pitfall. Prim needs the graph to be connected; on a disconnected graph it only spans the start vertex’s component (a minimum spanning forest requires restarting from each component). Also, only relax to vertices not yet in the tree — relaxing to in-tree vertices is wasted work and can corrupt keys if written carelessly.


11.10 Kruskal’s algorithm

Idea and pseudocode

Kruskal’s algorithm attacks the MST from the edges. It sorts all edges by increasing weight and scans them, adding an edge to a growing forest whenever it does not create a cycle — i.e. whenever its endpoints lie in different components. It stops after n1n-1 edges have been added.

The one nontrivial operation is testing “same component?” quickly, and updating components after a merge. That is exactly the job of a disjoint-set (union–find) data structure.

function Kruskal(G, w):
    A ← ∅                              # MST edge set
    for each vertex v: MakeSet(v)
    sort all edges of G by weight, ascending
    for each edge (u, v) in sorted order:
        if Find(u) ≠ Find(v):          # endpoints in different components
            A ← A ∪ { (u, v) }
            Union(u, v)
            if |A| = n - 1: break       # tree complete
    return A

The union–find (disjoint-set) data structure

Union–find maintains a partition of the vertices into disjoint sets — the components of the forest built so far — supporting three operations:

  • MakeSet(x) — put xx in its own singleton set;
  • Find(x) — return a canonical representative (root) of xx’s set; two elements are in the same set iff their representatives are equal;
  • Union(x, y) — merge the two sets containing xx and yy.

Union-find: disjoint sets stored as rooted trees; find follows to root, union links roots

Each set is stored as a rooted tree via a parent array (the root points to itself); Find walks parent pointers to the root. Two optimizations keep the trees flat:

  • Union by rank — each root carries a rank (an upper bound on its tree’s height). Union hangs the lower-rank root under the higher-rank root, so the height grows only when two equal-rank trees merge.
  • Path compression — during Find, re-point every node on the path directly to the root, so future queries are O(1)O(1).
function Find(x):
    if parent[x] ≠ x:
        parent[x] ← Find(parent[x])    # path compression
    return parent[x]

function Union(x, y):
    rx ← Find(x);  ry ← Find(y)
    if rx = ry: return
    if rank[rx] < rank[ry]: swap(rx, ry)   # union by rank: attach smaller under larger
    parent[ry] ← rx
    if rank[rx] = rank[ry]: rank[rx] ← rank[rx] + 1

Complexity. With both optimizations, any sequence of qq operations on nn elements runs in O(qα(n))O\big(q\,\alpha(n)\big) time (Tarjan, 1975), where α\alpha is the inverse Ackermann function — it grows so slowly that α(n)4\alpha(n)\le 4 for every nn that could be written down in the physical universe. So each operation is effectively constant time, “near-linear” overall. (Either optimization alone gives O(logn)O(\log n) per operation; together they are strictly better.)

A concrete union–find trace. Watch the structure through Kruskal’s unions on GG (shown next). Start with every vertex its own root, all ranks 00.

Operation Effect (parent / rank changes) Roots afterward
Union(B,C) equal ranks: parent[C]=B\text{parent}[C]{=}B, rank[B]=1\text{rank}[B]{=}1 B ⁣: ⁣{B,C}B\!:\!\{B,C\}
Union(A,C) Find(C)=B\text{Find}(C){=}B (rank 1) >> AA (rank 0): parent[A]=B\text{parent}[A]{=}B B ⁣: ⁣{A,B,C}B\!:\!\{A,B,C\}
Union(D,E) equal ranks: parent[E]=D\text{parent}[E]{=}D, rank[D]=1\text{rank}[D]{=}1 D ⁣: ⁣{D,E}D\!:\!\{D,E\}
Union(B,D) equal ranks 11: parent[D]=B\text{parent}[D]{=}B, rank[B]=2\text{rank}[B]{=}2 B ⁣: ⁣{A,B,C,D,E}B\!:\!\{A,B,C,D,E\}
Union(D,F) Find(D)=B\text{Find}(D){=}B (rank 2) >> FF (rank 0): parent[F]=B\text{parent}[F]{=}B B ⁣: ⁣{A,,F}B\!:\!\{A,\dots,F\}

After these, a call to Find(E) walks EDBE\to D\to B and, by path compression, re-points parent[E]B\text{parent}[E]\leftarrow B so the next Find(E) is a single hop — this is path compression in action.

Worked trace

Same graph GG. Sorted edges: BC(1),AC(2),DE(3),AB(4),BD(5),DF(6),EF(7),CD(8),CE(10)BC(1), AC(2), DE(3), AB(4), BD(5), DF(6), EF(7), CD(8), CE(10).

Edge ww Find(u)\text{Find}(u) vs Find(v)\text{Find}(v) Cycle? Action Components after
BCB\text{–}C 1 BCB\ne C no add {BC},{A},{D},{E},{F}\{BC\},\{A\},\{D\},\{E\},\{F\}
ACA\text{–}C 2 ACA\ne C no add {ABC},{D},{E},{F}\{ABC\},\{D\},\{E\},\{F\}
DED\text{–}E 3 DED\ne E no add {ABC},{DE},{F}\{ABC\},\{DE\},\{F\}
ABA\text{–}B 4 A=BA=B yes skip {ABC},{DE},{F}\{ABC\},\{DE\},\{F\}
BDB\text{–}D 5 BDB\ne D no add {ABCDE},{F}\{ABCDE\},\{F\}
DFD\text{–}F 6 DFD\ne F no add {ABCDEF}\{ABCDEF\}
EFE\text{–}F 7 (n1n{-}1 reached)
CDC\text{–}D 8
CEC\text{–}E 10

MST edges: {BC, AC, DE, BD, DF}\{B\text{–}C,\ A\text{–}C,\ D\text{–}E,\ B\text{–}D,\ D\text{–}F\}; total weight 1+2+3+5+6=171+2+3+5+6=\mathbf{17}.

This is exactly the tree Prim found (both equal {AC,BC,BD,DE,DF}\{AC,BC,BD,DE,DF\}, weight 1717) — as Corollary 11.8 guarantees, since GG’s weights are distinct. The edge AB(4)A\text{–}B(4) was skipped because AA and BB were already in the same component {ABC}\{ABC\}: adding it would close the cycle ACBAA\text{–}C\text{–}B\text{–}A, on which ABA\text{–}B is the heaviest edge.

Correctness

Theorem 11.10. Kruskal’s algorithm returns a minimum spanning tree.

Proof. Again we show the invariant the chosen set AA is contained in some MST, by induction on additions. The base case A=A=\varnothing is immediate.

Suppose AA\subseteq some MST and Kruskal is about to add edge e=(u,v)e=(u,v) because Find(u)Find(v)\text{Find}(u)\ne\text{Find}(v). Let SS be the component of uu in the current forest AA, and take the cut (S,VS)(S,V\setminus S). Every edge of AA lies within a single component, so no AA-edge crosses this cut: AA respects it. Moreover ee crosses it (its endpoints are in different components, one being SS).

Claim: ee is a light edge of this cut. Suppose some crossing edge ee' had w(e)<w(e)w(e')<w(e). Kruskal processes edges in increasing weight, so ee' was examined earlier. Since ee' crosses (S,VS)(S,V\setminus S), its endpoints lie in different components now; and because components only ever merge (union–find never splits), they were in different components then too — so ee' would have been added, i.e. eAe'\in A. But then ee' is an AA-edge crossing the cut, contradicting that AA respects it. Hence no crossing edge is lighter than ee; ee is light.

By the Cut Property (Theorem 11.6), ee is safe: A{e}A\cup\{e\} lies in some MST. The invariant holds. Skipped edges are harmless: an edge is skipped only when its endpoints are already connected by lighter edges, so it is the heaviest edge on the cycle it would close, and by the Cycle Property (Theorem 11.7) belongs to no MST. Finally, because GG is connected, Kruskal keeps finding cross-component edges until the forest becomes a single spanning tree of n1n-1 edges — an MST. \square

Complexity

  • Sorting the mm edges dominates: O(ElogE)=O(ElogV)O(E\log E)=O(E\log V) (as EV2E\le V^2 gives logE2logV\log E\le 2\log V).
  • Union–find: nn MakeSet, and at most 2E2E Find plus n1n-1 Union, all together O(Eα(V))O(E\,\alpha(V)) — essentially linear, and dominated by the sort.

So Kruskal runs in O(ElogV)O(E\log V). If the edges are already sorted, or weights are small integers sortable in O(E)O(E) by radix sort, the bottleneck disappears and Kruskal runs in O(Eα(V))O(E\,\alpha(V)) — nearly linear.

Common pitfall. Omitting union by rank (or path compression) is a real trap: naive Find up an unbalanced tree degrades to O(V)O(V) per query, making the union–find scan O(EV)O(EV) and silently dominating the sort. Keep both optimizations.

Prim vs Kruskal

Both produce an MST; here both return {AC,BC,BD,DE,DF}\{AC,BC,BD,DE,DF\} of weight 1717. They differ in the shape of the greed and in their ideal inputs:

  • Prim grows one connected tree from a seed, always taking the cheapest edge on its frontier; it is vertex-driven via a priority queue. Its array version is O(V2)O(V^2), independent of EE, so it shines on dense graphs.
  • Kruskal considers edges globally cheapest-first, gluing separate forest fragments; it is edge-driven via a sort plus union–find. It shines on sparse graphs and when edges arrive already sorted.

11.11 Comparison of all five algorithms

Algorithm Problem solved Greedy step Data structure Time Optimal?
Greedy vertex coloring proper vertex coloring smallest free color, per vertex adjacency list + scratch array O(V+E)O(V+E) no — Δ+1\le\Delta+1 colors
Greedy edge coloring proper edge coloring smallest free color, per edge adjacency list + scratch array O(EΔ)O(E\cdot\Delta) no — 2Δ1\le 2\Delta-1 colors
Dijkstra single-source shortest paths, w0w\ge 0 finalize nearest, relax edges binary heap (min-PQ) O((V+E)logV)O((V+E)\log V) yes
Prim minimum spanning tree add cheapest frontier edge binary heap (min-PQ) O((V+E)logV)O((V+E)\log V) yes
Kruskal minimum spanning tree add cheapest cycle-free edge sort + union–find O(ElogV)O(E\log V) yes

Comparison of the five greedy graph algorithms: problem, strategy, and complexity

Threads running through the table:

  • Greedy is universal here, but its guarantees differ. For shortest paths and MSTs a greedy step is provably safe — via the finalized-distance invariant (Theorem 11.5) and the cut property (Theorem 11.6). For coloring, greedy is only a bounded, order-sensitive approximation (Theorems 2, 4).
  • Dijkstra and Prim are structural twins. Both loop “extract the cheapest vertex, then relax its edges,” differing only in the meaning of the key: distance from the source (Dijkstra) versus weight of the cheapest edge into the tree (Prim). Their complexity profiles are identical. Kruskal is different in kind — edge-driven, needing union–find.
  • Watch the assumptions. Dijkstra requires non-negative weights (else Bellman–Ford); Prim and Kruskal require a connected graph (else they build a spanning forest); distinct weights make the MST unique.

Historical notes

  • Otakar Borůvka (1926) posed and solved the MST problem first, to lay an efficient electrical grid in Moravia; his parallel-flavored algorithm predates the others by decades.
  • Vojtěch Jarník (1930) described the tree-growing method now usually credited to Prim.
  • Joseph Kruskal (1956) published “On the shortest spanning subtree of a graph and the traveling salesman problem.”
  • Robert C. Prim (1957), at Bell Labs, rediscovered Jarník’s method; Edsger W. Dijkstra (1959) rediscovered it again and published his shortest-path algorithm in the same three-page note (Numerische Mathematik 1). Dijkstra later recalled designing the shortest-path algorithm in about twenty minutes, in his head, at an Amsterdam café. Because of this triple origin, Prim’s algorithm is sometimes called the Jarník–Prim or DJP algorithm.
  • R. L. Brooks (1941) proved χΔ\chi\le\Delta except for complete graphs and odd cycles; Vadim G. Vizing (1964) proved χ{Δ,Δ+1}\chi'\in\{\Delta,\Delta+1\}; Welsh and Powell (1967) introduced the largest-degree-first coloring heuristic in a paper on timetabling.
  • The union–find structure was introduced by Galler and Fisher (1964); its near-linear analysis is due to Hopcroft and Ullman (1973) (O(logn)O(\log^{*} n)) and, tightly, to Robert Tarjan (1975) (Θ(α(n))\Theta(\alpha(n))), with a matching lower bound by Fredman and Saks (1989).

Chapter summary

  • Every optimization algorithm here is greedy (the BFS/DFS traversals aside). For shortest paths and MSTs, greed is provably optimal; for coloring, it is only a bounded, order-dependent heuristic.
  • Breadth-first and depth-first search are the two systematic traversals: BFS explores in rings of increasing distance (a queue — and shortest paths in unweighted graphs), DFS plunges as deep as possible before backtracking (a stack/recursion). Both run in O(V+E)O(V+E) and underpin connectivity, component counting, and the tree/shortest-path algorithms below.
  • Greedy vertex coloring processes vertices in some order, assigning each the smallest color free among colored neighbors. It is always proper (Theorem 11.1), uses Δ+1\le\Delta+1 colors (Theorem 11.2, hence χΔ+1\chi\le\Delta+1), runs in O(V+E)O(V+E), but its color count depends on the order — on GG, one order used 33 (optimal), another 44. Welsh–Powell (largest degree first) is a good default.
  • Greedy edge coloring is the same idea on the line graph: always proper, 2Δ1\le 2\Delta-1 colors, O(EΔ)O(E\Delta). Vizing bounds the true optimum by Δ+1\Delta+1; on GG, greedy achieved χ(G)=Δ=4\chi'(G)=\Delta=4, its classes forming matchings (one perfect).
  • Dijkstra solves single-source shortest paths for non-negative weights by repeatedly finalizing the nearest vertex and relaxing its edges. Theorem 11.5 proves each finalized distance is correct via a shortest-path invariant, using non-negativity at exactly one step. With a binary heap it runs in O((V+E)logV)O((V+E)\log V). Negative weights break it — use Bellman–Ford (O(VE)O(VE), detects negative cycles). On GG from AA: A=0,B=3,C=2,D=8,E=11,F=14A{=}0,B{=}3,C{=}2,D{=}8,E{=}11,F{=}14.
  • MSTs are governed by the Cut Property (Theorem 11.6): a light edge across any cut respected by the current edge set is safe. This one lemma proves both Prim (grow one tree, add the cheapest frontier edge) and Kruskal (add globally cheapest cycle-free edges) correct. The Cycle Property justifies Kruskal’s skips. On GG both give the same unique MST {AC,BC,BD,DE,DF}\{AC,BC,BD,DE,DF\} of weight 1717.
  • Union–find with union by rank + path compression supports Kruskal’s cycle tests in amortized O(α(n))O(\alpha(n)) — effectively constant. Kruskal’s cost is dominated by the O(ElogV)O(E\log V) edge sort. Prim and Kruskal are the two greedy faces of the same optimization.

Exercises

Use the running graph GG (vertices AAFF; edges and weights in the table above) unless a problem says otherwise.

Warm-up

  1. Starting from vertex AA and visiting neighbours in alphabetical order, give the vertex-discovery order for (a) breadth-first and (b) depth-first search on GG. How many edges does each search tree contain, and why?
  2. Run greedy vertex coloring on GG in the order F,E,D,C,B,AF,E,D,C,B,A. How many colors does it use? Compare with the natural order A,,FA,\dots,F.
  3. State the two greedy upper bounds — Δ+1\Delta+1 colors for vertices, 2Δ12\Delta-1 for edges — and explain in one sentence each where the bound comes from.
  4. Run Dijkstra on GG from source FF. Give the final distance to every vertex and the shortest path to AA.
  5. What are χ(G)\chi(G) and χ(G)\chi'(G)? For each, give the lower-bound argument that proves the value greedy found is optimal.
  6. In Kruskal’s trace on GG the edge ABA\text{–}B was skipped. Which Find comparison caused the skip, and which cycle would ABA\text{–}B have closed?
  7. List the MST edges and total weight produced by (a) Prim from AA and (b) Kruskal. Are the edge sets identical, and why is that guaranteed here?

Standard

  1. Apply Welsh–Powell to GG (break degree ties alphabetically) and give the resulting coloring. Does it match the optimum?
  2. Prove that greedy vertex coloring always yields a proper coloring, for every ordering.
  3. Prove the bound χ(G)Δ+1\chi(G)\le\Delta+1 and state the graph GG’s values of Δ\Delta and χ\chi to show the bound need not be tight.
  4. In the Dijkstra trace, vertex EE was finalized at 1111, though it was first reached with tentative value 1212. Identify the relaxation that improved it, and explain why the later edge EFE\text{–}F did not change dist[F]\text{dist}[F].
  5. Give a small directed graph with one negative edge on which Dijkstra returns a wrong distance; show Dijkstra’s output and the true distance, and name the algorithm you would use instead.
  6. State the Cut Property and use it to justify Prim’s first edge choice on GG (from AA): exhibit the cut and the light edge.
  7. Perform Kruskal’s Union operations on GG using union by rank + path compression. Give the final parent and rank arrays, and describe what Find(E) returns and how it compresses the path.
  8. Suppose the weight of EFE\text{–}F is lowered from 77 to 00. Recompute the MST (edges and weight) and say which edge leaves the tree and why.

Challenge

  1. Prove the Cut Property (Theorem 11.6) in full, including the claim that a cycle crosses any cut an even number of times.
  2. Prove Dijkstra’s correctness (Theorem 11.5) and pinpoint the single step that requires non-negative edge weights.
  3. Prove that if all edge weights are distinct, the MST is unique. (Hint: combine the cut and cycle properties, or run an exchange argument.)
  4. For the crown graph Sn0S_n^0 (Kn,nK_{n,n} minus a perfect matching), prove that the ordering x0,y0,x1,y1,,xn1,yn1x_0,y_0,x_1,y_1,\dots,x_{n-1},y_{n-1} forces greedy to use nn colors, although χ=2\chi=2. What does this say about the tightness of the Δ+1\Delta+1 bound?
  5. Prove that union by rank alone (no path compression) keeps every tree of height hh with at least 2h2^h nodes, hence Find costs O(logn)O(\log n).
  6. Sketch the Bellman–Ford algorithm: its relaxation loop, why V1V-1 passes suffice on graphs without a negative cycle, how a VV-th improving pass detects a negative cycle, and its O(VE)O(VE) running time. Why is Dijkstra preferred when weights are non-negative?

Selected answers & hints

1. Both searches start at AA. (a) BFS discovers vertices in nondecreasing distance from the source (a queue); (b) DFS dives as deep as possible before backtracking (a stack/recursion). Each search tree has V1=5|V|-1=5 edges — a spanning tree of the connected graph GG — since every non-root vertex is reached across exactly one tree edge; the exact discovery order depends on the adjacency list.

2. Order F,E,D,C,B,AF,E,D,C,B,A gives C=1,F=1,B=2,E=2,A=3,D=3C{=}1,F{=}1,B{=}2,E{=}2,A{=}3,D{=}33 colors, same count as the natural order (both optimal, since χ(G)=3\chi(G)=3). The specific color labels differ, but the number does not.

3. Vertex: a vertex has Δ\le\Delta colored neighbors, so one of colors 1,,Δ+11,\dots,\Delta+1 is always free. Edge: an edge is adjacent to (degu1)+(degv1)2Δ2\le(\deg u-1)+(\deg v-1)\le 2\Delta-2 other edges, so one of 1,,2Δ11,\dots,2\Delta-1 is always free.

4. From FF the extraction order is F(0),D(6),E(7),B(11),C(12),A(14)F(0),D(6),E(7),B(11),C(12),A(14); final distances A=14,B=11,C=12,D=6,E=7,F=0A{=}14,B{=}11,C{=}12,D{=}6,E{=}7,F{=}0. Shortest path to AA: FDBCAF\to D\to B\to C\to A (weight 6+5+1+2=146+5+1+2=14).

5. χ(G)=3\chi(G)=3: the triangle ABCA\text{–}B\text{–}C forces 3\ge 3, and greedy exhibits a proper 33-coloring. χ(G)=4\chi'(G)=4: vertex CC has degree 44, so its four incident edges need 44 distinct colors (χΔ=4\chi'\ge\Delta=4), and greedy exhibits a proper 44-edge-coloring.

6. When ABA\text{–}B is examined, Find(A)=Find(B)\text{Find}(A)=\text{Find}(B) (both are in the component {A,B,C}\{A,B,C\} formed by adding BCB\text{–}C then ACA\text{–}C), so it is skipped; it would close the cycle ACBAA\text{–}C\text{–}B\text{–}A, on which it is the heaviest edge (4>2,14>2,1).

7. Both give {AC,BC,BD,DE,DF}\{A\text{–}C,B\text{–}C,B\text{–}D,D\text{–}E,D\text{–}F\}, weight 1717. The sets are identical because all nine weights are distinct, so the MST is unique (Corollary 11.8).

11. EE was first set to 1212 when CC was finalized (edge CEC\text{–}E, 2+102+10); it improved to 1111 when DD was finalized and edge DED\text{–}E gave 8+3=11<128+3=11<12. Later, from EE, edge EFE\text{–}F offers 11+7=1811+7=18, which is >dist[F]=14>\text{dist}[F]=14 (set via DFD\text{–}F), so no update.

12. Directed AB(2), AC(3), CB(2)A\to B(2),\ A\to C(3),\ C\to B(-2). Dijkstra finalizes BB at 22 (it is the current minimum) before examining CC, so it reports dist[B]=2\text{dist}[B]=2; but ACBA\to C\to B costs 32=13-2=1, the true distance. Use Bellman–Ford for negative weights.

14. Processing unions BC, AC, DE, BD, DFB{\cup}C,\ A{\cup}C,\ D{\cup}E,\ B{\cup}D,\ D{\cup}F yields root BB with rank[B]=2\text{rank}[B]=2; parent\text{parent}: AB, CB, DB, ED, FBA\to B,\ C\to B,\ D\to B,\ E\to D,\ F\to B. Find(E) walks EDBE\to D\to B, returns BB, and by path compression sets parent[E]B\text{parent}[E]\leftarrow B (so a subsequent Find(E) is one hop).

15. Lowering EFE\text{–}F to 00 makes it the cheapest edge; the new MST is {AC,BC,BD,DE,EF}\{A\text{–}C,B\text{–}C,B\text{–}D,D\text{–}E,E\text{–}F\} of weight 1111. The edge DF(6)D\text{–}F(6) leaves: in Kruskal, EFE\text{–}F now precedes it and already connects FF; equivalently, on the cycle DEFDD\text{–}E\text{–}F\text{–}D the heaviest edge is now DFD\text{–}F, which the cycle property excludes.

18. If two distinct MSTs T1T2T_1\ne T_2 existed, take the minimum-weight edge ee in their symmetric difference, say eT1e\in T_1. Adding ee to T2T_2 makes a cycle; by distinctness some cycle edge eT2T1e'\in T_2\setminus T_1 has w(e)>w(e)w(e')>w(e) (else ee would not be minimal in the difference), and swapping ee for ee' gives a lighter spanning tree than T2T_2 — contradiction. Hence the MST is unique.

19. By induction: x0x_0 gets 11; y0y_0 (non-adjacent to x0x_0) gets 11; and when we reach xk,ykx_k,y_k their already-colored neighbors are exactly {xi,yi:i<k}\{x_i,y_i : i<k\} minus the one partner they miss, which carry all of colors 1,,k1,\dots,k — so both get k+1k+1. Thus greedy uses nn colors while χ=2\chi=2, showing the Δ+1\Delta+1 (here Δ=n1\Delta=n-1) bound and the general optimality gap can be enormous under an adversarial order.

21. Bellman–Ford relaxes all mm edges, repeated n1n-1 times; since any shortest path has at most n1n-1 edges and each full pass correctly settles one more edge along it, n1n-1 passes suffice when there is no negative cycle. If an nn-th pass still relaxes some edge, a negative-weight cycle is reachable (distances would otherwise be stable), so report it. The cost is O(VE)O(VE). Dijkstra’s O(ElogV)O(E\log V) is far better, so prefer it whenever all weights are 0\ge 0.


Further reading

  • Rosen, Discrete Mathematics and Its Applications — §11.6 (shortest-path problems and Dijkstra’s algorithm), §11.8 (graph coloring), and §11.4–11.5 (spanning trees and minimum spanning trees, with Prim’s and Kruskal’s algorithms). Matches this course’s level and notation.
  • Cormen, Leiserson, Rivest, and Stein (CLRS), Introduction to Algorithms — the chapters on Minimum Spanning Trees (Kruskal and Prim), Single-Source Shortest Paths (Bellman–Ford and Dijkstra), and Data Structures for Disjoint Sets (union–find with the α(n)\alpha(n) analysis). In the 3rd edition these are Chapters 23, 24, and 21 respectively. The definitive source for the correctness proofs and complexity bounds sketched here.

Lectures/CDM-L11.md · 64.0 KB · updated 2026-08-01 21:14