Graph Algorithms
4.1 Breadth-First Search (BFS)
BFS explores the graph level by level from a source vertex.
BFS(G, s): for each v in V: d[v] = INF pi[v] = NIL d[s] = 0 Q = {s} while Q is not empty: u = Q.dequeue() for each v in G.adj[u]: if d[v] == INF: d[v] = d[u] + 1 pi[v] = u Q.enqueue(v)Theorem 4.1. BFS runs in time and discovers shortest paths (in terms of number of Edges) from the source to all reachable vertices.
Proof. Time: Each vertex is enqueued at most once and dequeued at most once: . Each edge is examined at most twice (once from each endpoint): . Total: .
Correctness: We prove by induction on the length of shortest paths. Let be a vertex discovered via edge . At the time is discovered, equals the shortest-path distance from to (induction hypothesis). Since BFS processes vertices in non-decreasing order of distance, is the shortest distance to . Any path to must go through some vertex at distance at most (the predecessor on the path), and all such vertices have already been processed.
Worked Example: BFS on a Weighted Path Problem
Find the shortest path (fewest edges) from to all other vertices in the graph with edges: .
Starting from :
- Level 0: (distance 0)
- Level 1: (distance 1)
- Level 2: (distance 2)
- Level 3: (distance 3)
Shortest path from to : (or or ), all of length 3.
4.2 Depth-First Search (DFS)
DFS explores as far as possible along each branch before backtracking.
DFS(G): for each v in V: colour[v] = WHITE pi[v] = NIL time = 0 for each v in V: if colour[v] == WHITE: DFS-Visit(G, v)
DFS-Visit(G, u): colour[u] = GREY time += 1 d[u] = time for each v in G.adj[u]: if colour[v] == WHITE: pi[v] = u DFS-Visit(G, v) colour[u] = BLACK time += 1 f[u] = timeTheorem 4.2. DFS runs in time and can be used to detect cycles, find connected Components, compute topological orderings, and identify strongly connected components.
Proof (time). Each vertex is coloured exactly once (from WHITE to GREY) and finished exactly once (from GREY to BLACK): . Each edge is examined at most twice (once in each direction for undirected, once for directed): . Total: .
Theorem 4.3 (Parenthesis Theorem). In any DFS, for any two vertices and Exactly one of the following holds: (1) (interval nesting), (2) (interval nesting), or (3) the intervals and are disjoint.
Proof. The DFS call stack forms a nesting of intervals. When we start visiting from We must finish before finishing Giving nesting. If and are in different DFS trees, their intervals are disjoint.
Theorem 4.4 (White-Path Theorem). is a descendant of in the DFS forest if and only if, at the time is discovered, there is a path from to consisting entirely of white vertices.
Proof. () By induction on the depth of in the DFS tree. If is a child of Then was white when discovered from . If is a deeper descendant, the path goes through intermediate white vertices.
() Suppose there is a white path from to at time . Let be the first vertex on this path discovered after . All vertices before on the path are still white (they can only be discovered after ), so will be discovered from the path. By induction, is a descendant of Hence of .
4.3 Topological Sort
A topological ordering of a DAG is a linear ordering of vertices such that for every directed edge , appears before .
Algorithm: Run DFS on the DAG. Output vertices in reverse order of finishing times.
Theorem 4.5. The reverse post-order of a DFS on a DAG produces a valid topological ordering.
Proof. Suppose there is an edge but appears after in the ordering (i.e., ). Since is an edge, when is being explored (coloured GREY), if is WHITE, then is discovered as a descendant of So Contradiction. If is GREY, we have a back edge, implying a cycle, contradicting that the graph is acyclic. If is BLACK, then Contradicting .
4.4 Strongly Connected Components
Two vertices and are strongly connected if there is a path from to and from to . A strongly connected component (SCC) is a maximal set of strongly connected vertices.
Kosaraju”s Algorithm:
- Run DFS on Recording finishing times.
- Compute (transpose of : reverse all edges).
- Run DFS on in decreasing order of finishing times. Each DFS tree is an SCC.
Theorem 4.6. Kosaraju’s algorithm correctly identifies all SCCs in time.
Proof. Let be the SCC containing the vertex with the highest finishing time in the first DFS. We claim that in No vertex in can reach a vertex outside (otherwise would have a path to and from that vertex, placing it in ). In the second DFS, starting from in We discover exactly the vertices in . Removing and repeating the argument gives the remaining SCCs.
Tarjan’s Algorithm finds SCCs in a single DFS pass using a stack and low-link values, also in time.
4.5 Dijkstra’s Algorithm
Problem. Find shortest paths from a single source in a weighted graph with non-negative edge Weights.
Algorithm. Maintain a priority queue of vertices keyed by their current shortest-path estimate. Repeatedly extract the vertex with minimum distance and relax its edges.
Dijkstra(G, w, s): for each v in V: d[v] = INF pi[v] = NIL d[s] = 0 S = {} // processed vertices Q = V // priority queue keyed by d[] while Q is not empty: u = ExtractMin(Q) S = S ∪ {u} for each v in G.adj[u]: if d[v] > d[u] + w(u, v): d[v] = d[u] + w(u, v) pi[v] = u DecreaseKey(Q, v, d[v])Theorem 4.7. Dijkstra’s algorithm correctly computes shortest paths from the source.
Proof. We prove by induction on that when a vertex is added to , (the true shortest-path distance).
Base case: is the first vertex added, and .
Inductive step: Suppose all vertices in have correct distances. Let be the next vertex extracted from . Suppose for contradiction that . Consider a shortest path from to And let be the first edge on where and . Then (by induction). When was added to The edge was relaxed, so . Since edge weights are non-negative, . But And is in Contradicting that has the minimum -value in .
Theorem 4.8. Dijkstra’s algorithm with a binary heap runs in time. With a Fibonacci heap: .
Proof. Each vertex is extracted from the priority queue at most once: . Each edge is Relaxed at most once, and each relaxation may cause a DecreaseKey: . Total: . With a Fibonacci heap, DecreaseKey is amortised, giving .
Worked Example: Dijkstra's Algorithm
Find shortest paths from in the graph:
- ,
- ,
- ,
Initial: ,
Extract : relax , .
Extract : relax , .
Extract : relax , .
Extract : relax .
Extract : no improvements.
Result: , , , ,
4.6 Bellman-Ford Algorithm
Problem. Find shortest paths from a single source, allowing negative edge weights (but no negative Cycles).
BellmanFord(G, w, s): for each v in V: d[v] = INF pi[v] = NIL d[s] = 0 for i = 1 to |V| - 1: for each edge (u, v) in E: if d[v] > d[u] + w(u, v): d[v] = d[u] + w(u, v) pi[v] = u for each edge (u, v) in E: if d[v] > d[u] + w(u, v): return "negative cycle detected"Theorem 4.9. Bellman-Ford runs in time. It correctly detects negative-weight cycles.
Proof. After iterations of the relaxation loop, is at most the weight of the shortest path from to using at most edges. This is proved by induction on .
Base case (): Only is finite, which is the weight of the empty path.
Inductive step: A shortest path from to using at most edges either uses at most edges (handled by induction) or uses exactly edges, ending with edge where the shortest path to uses at most edges. The relaxation of in iteration handles this case.
After iterations, all shortest paths (which have at most edges) are correct. A -th iteration that updates any distance indicates a path of length with strictly decreasing weight, which contains a cycle of negative weight.
Worked Example: Bellman-Ford with Negative Edge Weights
Find shortest paths from in the graph:
- ,
- , ,
- ,
- ,
Initial: for . .
Iteration 1: Relax all edges.
- : . : .
- : . : . : .
- : . : .
- : . : .
- : . After iter 1: .
Iteration 2: Relax all edges.
- : . : .
- : . : .
- : . No changes: .
Iteration 3—5: No changes. Algorithm terminates.
Negative cycle check: No edge can be relaxed. No negative cycle.
Result: . Shortest path to : Cost .
Worked Example: Bellman-Ford Negative Cycle Detection
Graph: , , . This has a cycle of weight . Not negative.
Now add . Cycle weight: . Negative cycle.
Initial: Rest .
Iteration 1: , , .
Iteration 2: , , .
Iteration 3: , , .
Iteration 4: , , .
Iteration 5: , , .
Check (iteration 6): : Can still relax. Negative cycle detected!
4.7 Floyd-Warshall Algorithm
Problem. Find all-pairs shortest paths.
Algorithm. For : for each pair Check if going through vertex Improves the path.
Derivation. Define as the shortest-path distance from to using only intermediate vertices from . Then:
- (the weight of edge Or if no edge).
- For : The shortest path from to through vertices either does not use vertex (giving ) or uses vertex (giving ).
Theorem 4.10. Floyd-Warshall runs in time and space.
Proof. The triple nested loop (, , ) executes iterations, each doing work. The distance matrix requires space. Note that can overwrite in place because and (paths from to and to using vertices up to cannot be improved by going through again without a negative cycle).
Worked Example: Floyd-Warshall on 4 Vertices
Find all-pairs shortest paths for the graph with vertices and edges: , , , , , , .
Initial distance matrix :
(through vertex 1): . . . . . .
(through vertex 2): . . . . . .
(through vertex 3): . . . . . .
(through vertex 4): . . . . . .
4.8 Minimum Spanning Tree
Kruskal’s Algorithm. Sort edges by weight, add edges to the MST as long as they don’t create a Cycle (using Union-Find). .
Prim’s Algorithm. Start from any vertex, repeatedly add the minimum-weight edge connecting the Tree to a non-tree vertex (using a priority queue). .
Theorem 4.11 (Cut Property). For any cut of the graph, the minimum-weight edge crossing the cut Belongs to some MST.
Proof. Let be a cut and be the minimum-weight crossing edge with , . Let be an MST. If We are done. Otherwise, adding to creates a cycle. This cycle must cross the cut at least once more (it goes from to via some other path). Let be another crossing edge on this cycle. Since is the minimum-weight crossing edge, . Replacing with in gives a spanning tree of weight no greater than Hence an MST containing .
Theorem 4.12 (Cycle Property). For any cycle, the maximum-weight edge on the cycle does not belong To any MST.
Proof. Let be a cycle and be the maximum-weight edge on . Let be an MST. If We are done. Otherwise, removing from disconnects it into two components. The rest of cycle must contain an edge crossing this cut. Since (if We can replace either), replacing with gives a spanning tree of strictly smaller weight, contradicting the optimality of .
Theorem 4.13. Kruskal’s algorithm produces a minimum spanning tree.
Proof. We prove by induction on the number of edges added. At each step, the algorithm adds the minimum-weight edge that does not create a cycle. Consider the cut formed by the two connected components that the new edge connects. By the cut property, this minimum-weight crossing edge belongs to some MST. Since all previously added edges are in every MST (by induction), the edge set maintained by the algorithm is always a subset of some MST.
Theorem 4.14. Prim’s algorithm produces a minimum spanning tree.
Proof. By induction on the size of the tree. At each step, Prim’s adds the minimum-weight edge connecting the current tree to a vertex outside . Consider the cut . By the cut property, the minimum-weight crossing edge belongs to some MST.
Worked Example: Kruskal's Algorithm
Find the MST of the graph with edges (sorted by weight): , , , , , , , .
Vertices: .
Sorted edges: , , , , , , , .
Process each edge:
- : Add. Forest: . Cost: 2.
- : Add. Forest: . Cost: 5.
- : Add. Forest: , . Cost: 9.
- : Add (connects two components). Forest: . Cost: 14.
- : Skip (creates cycle ).
- : Skip (creates cycle).
- : Skip (creates cycle).
- : Skip (creates cycle).
MST: , , , . Total weight: 14.
Worked Example: Prim's Algorithm
Find the MST of the same graph starting from vertex .
Edges from : , , . Minimum: . Tree: . Cost: 4.
Edges crossing cut: , , , . Minimum: . Tree: . Cost: 9.
Edges crossing cut: , , , , . Minimum: . Tree: . Cost: 12.
Edges crossing cut: , , . Minimum: . Tree: . Cost: 14.
MST: , , , . Total weight: 14 (same as Kruskal’s).