LESSON 03 · UNIT 3 AOS 2 WEIGHTED GRAPH ALGORITHMS

Minimum Spanning Trees, Shortest Paths, A* & PageRank

Weighted graphs attach a numerical cost w(e)w(e) to each edge, enabling network-wide structural optimisation: spanning all vertices at minimum total connection cost (MST), finding minimum-weight paths (SSSP / APSP), guiding search with admissible heuristics (A*), and ranking network authority recursively (PageRank).

01 · MINIMUM SPANNING TREES

prim's & kruskal's mst algorithms.

Given a connected, undirected weighted graph G=(V,E,w)G = (V, E, w), a Minimum Spanning Tree (MST) is a spanning tree T=(V,ET)T = (V, E_T) with ∣ET∣=∣V∣−1|E_T| = |V| - 1 edges that minimises the total edge weight w(T)=∑e∈ETw(e)w(T) = \sum_{e \in E_T} w(e).

THE CUT PROPERTY & NEGATIVE WEIGHTS THEORETICAL FOUNDATION

A cut (S,V∖S)(S, V \setminus S) partitions the vertices VV into two non-empty disjoint sets. The Cut Property guarantees that the minimum-weight edge with one endpoint in SS and the other in V∖SV \setminus S is always safe to include in an MST. Unlike Dijkstra's algorithm, both Prim's and Kruskal's algorithms work correctly on graphs with negative edge weights because comparing single cross-cut edges never relies on path prefix monotonicity.

PRIM'S ALGORITHM · NESTED LOOP ARRAY Q HESS PSEUDOCODE · O(|V|²)
Algorithm Prim_Array(G, v0):
    For each vertex v in G do:
        cost[v] <- infinity
        prev[v] <- null
    End do
    cost[v0] <- 0
    Q <- list of all vertices in G

    While Q is not empty do:
        u <- vertex in Q with minimum cost[u]
        remove u from Q
        For each neighbour v of u do:
            If v in Q and weight(u, v) < cost[v] then:
                cost[v] <- weight(u, v)
                prev[v] <- u
            End if
        End do
    End do
    Return prev
End Algorithm
PRIM'S ALGORITHM · MIN-PRIORITY QUEUE HESS PSEUDOCODE · -∞ INIT FOR NEGATIVE EDGES
Algorithm Prim_PQ(G, v0):
    Q <- new Min-Priority Queue
    For each vertex v in G do:
        cost[v] <- infinity
        prev[v] <- null
        enqueue(Q, v, cost[v])
    End do
    // -infinity ensures v0 is pulled first even with negative edges!
    cost[v0] <- -infinity
    requeue(Q, v0, cost[v0])

    While Q is not empty do:
        u <- peek(Q); dequeue(Q)
        For each (u, v) with weight w in Edges(G, u) do:
            If v in Q and w < cost[v] then:
                cost[v] <- w
                prev[v] <- u
                requeue(Q, v, w)
            End if
        End do
    End do
    Return prev
End Algorithm
KRUSKAL'S ALGORITHM · MERGING A FOREST VCE PSEUDOCODE · O(|E| log |V|)
Algorithm Kruskal(G):
    T <- Graph with all vertices V and no edges
    L <- list of all edges in E, sorted by weight ascending
    For each edge (u, v) in L do:
        If adding (u, v) to T does not create a cycle then:
            Add edge (u, v) to T
        End If
        If |E(T)| == |V| - 1 then:
            Return T
        End If
    End For
    Return T
End Algorithm
MST ALGORITHM COMPLEXITY COMPARISON
ALGORITHM & IMPLEMENTATION TIME COMPLEXITY OPTIMAL GRAPH REGIME
Prim's (Unsorted Array / Adjacency Matrix) O(∣V∣2)\mathcal{O}(|V|^2) Best for dense graphs where ∣E∣≈∣V∣2|E| \approx |V|^2 (avoids heap log factor).
Prim's (Binary Min-Heap Priority Queue) O(∣E∣log⁡∣V∣)\mathcal{O}(|E| \log |V|) Best for sparse graphs where ∣E∣≪∣V∣2|E| \ll |V|^2.
Kruskal's (Sorted Edges + Union-Find) O(∣E∣log⁡∣E∣)=O(∣E∣log⁡∣V∣)\mathcal{O}(|E| \log |E|) = \mathcal{O}(|E| \log |V|) Dominated by sorting ∣E∣|E| edges; ideal for sparse edge-list graphs.
02 · CORRECTNESS & EXAM TRACE METHOD

prim's manual table method & loop invariant proof.

HESS Manual Table Method for Hand-Tracing Prim's Algorithm

In the HESS study guide, Prim's algorithm is hand-traced using a (distance, parent) state table where each column represents a vertex v∈Vv \in V and each row represents the state of the Priority Queue after adding a vertex to the MST:

  1. Create a table with columns for every vertex (in lexicographical/alphabetical order). Initialise all cells to (∞, nil).
  2. Row 1 (Initialisation): Set the starting (lexicographically first) vertex v0v_0 to (0, nil) and all other vertices to (∞, nil). Circle (0, nil).
  3. Update Neighbours: Add the circled vertex uu as the label of the next row. For every unvisited vertex vv, if the edge weight w(u,v)w(u, v) is smaller than vv's current distance in the previous row, update cell vv to (w(u, v), u); otherwise, copy its previous (distance, parent) entry down.
  4. Select Minimum & Tie-Breaking: In the current row, circle the unvisited cell with the minimum distance, breaking any ties in lexicographical (alphabetical) order. Cross out / lock that column for subsequent rows.
  5. Repeat until all ∣V∣|V| vertices have been circled; the circled (distance, parent) pairs directly list the ∣V∣−1|V|-1 MST edges and their weights!

Formal Correctness Proof of Prim's Algorithm (Loop Invariant)

In the HESS notes, Prim's algorithm is proven correct by establishing the Loop Invariant: At the start of each iteration of the while loop, the subtree TT is a subgraph of some Minimum Spanning Tree T∗T^* of GG.

1. INITIALIZATION (BASE CASE) BEFORE ITERATION 1

Initially, TT consists of a single starting vertex xx and zero edges. Since every spanning tree of GG contains all vertices in VV, TT is trivially a subgraph of every MST T∗T^*.

3. TERMINATION LOOP EXIT

The loop terminates when TT contains all ∣V∣|V| vertices and ∣V∣−1|V|-1 edges. Since TT is connected, acyclic, spans VV, and is a subgraph of an MST T∗T^*, T=T∗T = T^* is a Minimum Spanning Tree.

2. MAINTENANCE (EXCHANGE ARGUMENT BY CONTRADICTION) INDUCTIVE STEP

Suppose T⊆T∗T \subseteq T^* before an iteration, and Prim's chooses the minimum-weight edge e=(u,v)e = (u, v) crossing the cut between V(T)V(T) and V∖V(T)V \setminus V(T). If e∈T∗e \in T^*, then T∪{e}⊆T∗T \cup \{e\} \subseteq T^* holds immediately.

Otherwise, suppose e∉T∗e \notin T^*. Adding ee to T∗T^* creates a unique cycle CC. Because u∈V(T)u \in V(T) and v∉V(T)v \notin V(T), the cycle CC must contain at least one other edge e′≠ee' \neq e that also crosses the cut (V(T),V∖V(T))(V(T), V \setminus V(T)). Since Prim's selected the minimum-weight cross-cut edge, w(e)≤w(e′)w(e) \le w(e'). Forming a new spanning tree T′=(T∗∖{e′})∪{e}T' = (T^* \setminus \{e'\}) \cup \{e\} gives total weight w(T′)=w(T∗)−w(e′)+w(e)≤w(T∗)w(T') = w(T^*) - w(e') + w(e) \le w(T^*). Since T∗T^* is an MST, w(T′)=w(T∗)w(T') = w(T^*), so T′T' is also an MST and T∪{e}⊆T′T \cup \{e\} \subseteq T', preserving the loop invariant!

03 · SINGLE-SOURCE SHORTEST PATHS (SSSP)

dijkstra's & bellman-ford algorithms.

The Single-Source Shortest Path problem asks for the minimum-weight path from a starting source vertex ss to every other vertex v∈Vv \in V.

DIJKSTRA'S ALGORITHM · MIN-PRIORITY QUEUE NON-NEGATIVE WEIGHTS ONLY
Algorithm Dijkstra(G, source):
    let Q be a Min-Priority Queue
    dist[source] <- 0
    prev[source] <- undefined
    For each vertex v in G do:
        If v != source then:
            dist[v] <- infinity
            prev[v] <- undefined
        End If
        Q.insertWithPriority(v, dist[v])
    End For

    While Q is not empty do:
        u <- Q.pullMin()
        For each neighbour v of u do:
            alt <- dist[u] + weight(u, v)
            If alt < dist[v] then:
                dist[v] <- alt
                prev[v] <- u
                Q.decreasePriority(v, alt)
            End If
        End For
    End While
    Return dist[], prev[]
End Algorithm
BELLMAN-FORD ALGORITHM · DYNAMIC PROGRAMMING HANDLES NEGATIVE WEIGHTS & CYCLES
Algorithm BellmanFord(G, source):
    For each vertex v in V do:
        dist[v] <- infinity
        prev[v] <- null
    End For
    dist[source] <- 0

    // Relax all edges |V| - 1 times
    For i from 1 to |V| - 1 do:
        For each edge (u, v) with weight w in E do:
            If dist[u] + w < dist[v] then:
                dist[v] <- dist[u] + w
                prev[v] <- u
            End If
        End For
    End For

    // |V|-th pass: check for negative-weight cycles
    For each edge (u, v) with weight w in E do:
        If dist[u] + w < dist[v] then:
            Error "Graph contains a negative-weight cycle"
        End If
    End For
    Return dist[], prev[]
End Algorithm
  • Why Dijkstra Fails on Negative Edges: Dijkstra greedily locks in the vertex uu with the smallest tentative distance dist[u]\text{dist}[u], assuming adding future edges can only increase path cost. A negative edge w(x,u)<0w(x, u) < 0 discovered later can reduce the cost to uu after uu has already been extracted from the Priority Queue.
  • Why Bellman-Ford Relaxes ∣V∣−1|V| - 1 Times: Any simple shortest path (without cycles) in a graph of ∣V∣|V| vertices contains at most ∣V∣−1|V| - 1 edges. Relaxing every edge in EE across ∣V∣−1|V| - 1 passes (Θ(∣V∣⋅∣E∣)\Theta(|V| \cdot |E|) time) guarantees optimal distances unless a negative-weight cycle is reachable, which is caught if any edge can still be relaxed on the ∣V∣|V|-th pass.
04 · ALL-PAIRS MATRIX DYNAMIC PROGRAMMING

warshall's & floyd-warshall's algorithms.

Both Warshall's Algorithm (transitive closure / reachability) and Floyd-Warshall's Algorithm (all-pairs shortest paths) use Dynamic Programming over an n×nn \times n matrix (n=∣V∣n = |V|), incrementally allowing vertices {1,2,…,k}\{1, 2, \dots, k\} as intermediate stepping stones. Both run in Θ(∣V∣3)\Theta(|V|^3) time and Θ(∣V∣2)\Theta(|V|^2) space.

In the HESS notes, Warshall's and Floyd-Warshall's qualify as Dynamic Programming for two foundational reasons: (1) they decompose paths into trivial subproblems (direct edges of length 1 when k=0k = 0) versus non-trivial subproblems (paths routed through intermediate vertices {1…k}\{1 \dots k\}), and (2) they exhibit overlapping subproblems (every path from ii to any jj via kk reuses the cached sub-path (i,k)(i, k)). Furthermore, Floyd-Warshall is structurally identical to Warshall's algorithm under min-plus algebra (replacing Boolean ∨\lor with min⁡\min and Boolean ∧\land with ++).

Ti,j(k)={Ai,jif k=0Ti,j(k−1)∨(Ti,k(k−1)∧Tk,j(k−1))if 0<k≤∣V∣T_{i,j}^{(k)} = \begin{cases} A_{i,j} & \text{if } k = 0 \\[4pt] T_{i,j}^{(k-1)} \lor \left( T_{i,k}^{(k-1)} \land T_{k,j}^{(k-1)} \right) & \text{if } 0 < k \le |V| \end{cases}
Wi,j(k)={Wi,jif k=0min⁡ ⁣(Wi,j(k−1),  Wi,k(k−1)+Wk,j(k−1))if 0<k≤∣V∣W_{i,j}^{(k)} = \begin{cases} W_{i,j} & \text{if } k = 0 \\[4pt] \min\!\left( W_{i,j}^{(k-1)},\; W_{i,k}^{(k-1)} + W_{k,j}^{(k-1)} \right) & \text{if } 0 < k \le |V| \end{cases}
WARSHALL'S ALGORITHM (WITH OUTER-LOOP OPTIMISATION) Θ(|V|³) BOOLEAN DP
Algorithm Warshall_Improved(A[1..n, 1..n]):
    T <- A // k = 0 base case: Boolean adjacency matrix
    For k from 1 to n do:
        For i from 1 to n do:
            // HESS Optimisation: only scan row j if i can reach k!
            If T[i, k] == 1 then:
                For j from 1 to n do:
                    T[i, j] <- T[i, j] OR T[k, j]
                End For
            End If
        End For
    End For
    Return T
End Algorithm
FLOYD-WARSHALL'S ALGORITHM · ALL-PAIRS SHORTEST PATH Θ(|V|³) MIN-PLUS DP
Algorithm FloydWarshall(G):
    let dist be a |V| x |V| matrix initialised to infinity
    For each vertex v in V do:
        dist[v, v] <- 0
    End For
    For each edge (u, v) with weight w in E do:
        dist[u, v] <- w
    End For

    For k from 1 to |V| do:
        For i from 1 to |V| do:
            For j from 1 to |V| do:
                If dist[i, k] + dist[k, j] < dist[i, j] then:
                    dist[i, j] <- dist[i, k] + dist[k, j]
                End If
            End For
        End For
    End For
    Return dist
End Algorithm
DETECTING NEGATIVE CYCLES IN FLOYD-WARSHALL DIAGONAL CHECK

Initially, the main diagonal entries are dist[i,i]=0\text{dist}[i, i] = 0. If the graph contains a negative-weight cycle reachable from vertex ii, the path from ii around the cycle and back to ii has total cost <0< 0, causing dist[i,i]<0\text{dist}[i, i] < 0 on the main diagonal!

06 · RECURSIVE NETWORK CENTRALITY

recursion & google pagerank algorithm.

PageRank models the web as a directed graph G=(V,E)G = (V, E) where vertices are webpages and directed edges (B,A)(B, A) are hyperlinks from page BB to page AA. A page's importance is defined recursively by the importance of the pages linking to it, divided by their total outbound links L(B)L(B):

PR(Ai)=1−d∣V∣+d∑B∈M(A)PR(Bi−1)L(B)PR(A_i) = \frac{1 - d}{|V|} + d \sum_{B \in M(A)} \frac{PR(B_{i-1})}{L(B)}
  • PR(Ai)PR(A_i): The PageRank of node AA at iteration ii (initialised at i=0i = 0 to PR(v0)=1∣V∣PR(v_0) = \frac{1}{|V|} for all nodes).
  • dd: The damping factor (typically d=0.85d = 0.85), representing the probability that a random surfer clicks a link on the current page rather than teleporting to a random page (1−d=0.151 - d = 0.15).
  • M(A)M(A): The set of pages that have an outbound link pointing into AA.
  • L(B)L(B): The out-degree deg⁡+(B)\deg^+(B) (total outbound links from page BB).
  • Sink Nodes (Dead Ends, outdegree == 0): If a page SS has no outbound links (L(S)=0L(S) = 0), it is a sink node that would drain PageRank from the system. In the HESS specification, before running PageRank iterations, every sink node has virtual outbound edges added to every node in the graph, including itself (so L(S)=∣V∣L(S) = |V|).
Prim's Cut Property Exchange Argument and HESS 4-Node PageRank Network Left panel shows Prim's Cut Property separating V(T) from V \ V(T) with minimum cross-cut edge e and alternative cycle edge e'. Right panel shows the HESS 4-node PageRank directed graph where Node 2 is a sink node. PRIM'S CUT PROPERTY (V(T), V\V(T)) V(T) V \ V(T) e = min cut edge e' in T* (w(e) <= w(e')) u v x y T' = (T* - e') U (e) is an MST HESS 4-NODE PAGERANK (NODE 2 SINK) 1 2* 3 4 2* is a SINK (outdeg=0 → links to 1,2,3,4; L(2)=4)
FIGURE 03 · PRIM'S CUT EXCHANGE ARGUMENT & HESS 4-NODE PAGERANK GRAPH WITH SINK NODE 2

Worked Example from HESS Notes: 4-Node Web Graph (Nodes 1, 2, 3, 4 with Sink Node 2)

In the HESS study notes, consider the 4-node directed graph V={1,2,3,4}V = \{1, 2, 3, 4\} (∣V∣=4|V| = 4, damping factor d=0.85d = 0.85, teleportation term 1−0.854=0.0375\frac{1 - 0.85}{4} = 0.0375) with directed edges:

  • Node 1 links to Node 2 and Node 3 (L(1)=2L(1) = 2).
  • Node 2 has zero outbound links (outdegree=0\text{outdegree} = 0)—it is a sink node! By the HESS sink-node rule, virtual outbound links are added from Node 2 to all 4 nodes (1, 2, 3, and 4, including Node 2 itself), giving L(2)=4L(2) = 4.
  • Node 3 links to Node 2 and Node 4 (L(3)=2L(3) = 2).
  • Node 4 links to Node 3 (L(4)=1L(4) = 1).
PR(1i)=1−0.854+0.85(PR(2i−1)4)PR(2i)=1−0.854+0.85(PR(1i−1)2+PR(3i−1)2)PR(3i)=1−0.854+0.85(PR(1i−1)2+PR(2i−1)4+PR(4i−1)1)PR(4i)=1−0.854+0.85(PR(2i−1)4+PR(3i−1)1)\begin{aligned} PR(1_i) &= \frac{1 - 0.85}{4} + 0.85\left(\frac{PR(2_{i-1})}{4}\right) \\[4pt] PR(2_i) &= \frac{1 - 0.85}{4} + 0.85\left(\frac{PR(1_{i-1})}{2} + \frac{PR(3_{i-1})}{2}\right) \\[4pt] PR(3_i) &= \frac{1 - 0.85}{4} + 0.85\left(\frac{PR(1_{i-1})}{2} + \frac{PR(2_{i-1})}{4} + \frac{PR(4_{i-1})}{1}\right) \\[4pt] PR(4_i) &= \frac{1 - 0.85}{4} + 0.85\left(\frac{PR(2_{i-1})}{4} + \frac{PR(3_{i-1})}{1}\right) \end{aligned}
HESS PAGERANK ITERATION CONVERGENCE TABLE (NODES 1, 2, 3, 4 · d=0.85d=0.85)
ITERATION NODE 1 NODE 2 (SINK) NODE 3 NODE 4 SUM (∑PR\sum PR)
Iteration 0 (i=0i=0) 0.25 0.25 0.25 0.25 1.00
Iteration 1 (i=1i=1) 0.09 0.30 0.41 0.20 1.00
Iteration 2 (i=2i=2) 0.10 0.31 0.31 0.28 1.00
Iteration 3 (i=3i=3) 0.10 0.28 0.38 0.23 1.00
Iteration 4 (i=4i=4) 0.10 0.30 0.34 0.26 1.00
Iteration 10 (Converged) 0.10 (Rank 4) 0.29 (Rank 2) 0.36 (Rank 1) 0.25 (Rank 3) 1.00 (3>2>4>13 > 2 > 4 > 1)
ALGORITHM · GOOGLE PAGERANK WITH SINK-NODE PREPROCESSING HESS PSEUDOCODE
Algorithm PageRank(G, d, iterations):
    N <- |V(G)|
    // Step 1: Convert sink nodes (outdegree = 0) by linking to all nodes
    For each node n in V(G) do:
        If n.outdegree == 0 then:
            For each node m in V(G) do:
                add directed edge (n, m) to G
            End do
            n.outdegree <- N
        End if
        PR[n] <- 1 / N
    End do

    // Step 2: Iterative power-method update
    For iter from 1 to iterations do:
        For each node u in V(G) do:
            sum <- 0
            For each node v in Inbound(G, u) do:
                sum <- sum + (PR[v] / v.outdegree)
            End do
            newPR[u] <- ((1 - d) / N) + d * sum
        End do
        PR <- newPR
    End do
    Return PR
End Algorithm
Self-Check: Compare the capabilities of Dijkstra, Bellman-Ford, Prim, and Floyd-Warshall when negative edge weights are present.
  • Prim's & Kruskal's (MST): Always work correctly with negative edge weights (even if negative cycles exist, since trees cannot contain cycles).
  • Dijkstra's (SSSP): Fails on negative edge weights because once a vertex is popped from the Min-PQ, its distance is assumed final.
  • Bellman-Ford (SSSP): Works with negative edge weights and detects reachable negative-weight cycles on the ∣V∣|V|-th pass.
  • Floyd-Warshall (APSP): Works with negative edge weights and detects negative-weight cycles whenever any diagonal entry becomes negative (dist[i,i]<0\text{dist}[i, i] < 0).