Patterns/Part IV - Core Algorithms/Shortest Path

Pattern Reference

Shortest Path

"Dijkstra, Bellman-Ford, Floyd-Warshall, SPFA, 0-1 BFS, A*."

Loading...

Deep Dive Tutorial

When graph edges have weights, BFS no longer gives shortest paths. You need one of three algorithms: Dijkstra (greedy, non-negative weights), Bellman-Ford (DP, handles negative weights), or Floyd-Warshall (all-pairs DP). The right choice depends on whether negative weights exist, whether you need single-source or all-pairs, and the graph density.

AlgorithmTimeHandles Negatives?Use When
BFSO(V+E)No (unweighted)All edges weight 1
Dijkstra (heap)O((V+E) log V)NoNon-negative weights, single source
Bellman-FordO(V·E)YesNegative weights; negative cycle detection
Floyd-WarshallO(V³)Yes (no neg cycles)All pairs, dense graph, V ≤ 500
0-1 BFSO(V+E)NoEdge weights only 0 or 1
SPFAO(V·E) worstYesFaster Bellman-Ford in practice (risky)

Dijkstra's Algorithm

lightbulb
Dijkstra's key insight: Greedily process the unvisited node with the smallest known distance. Since all edge weights ≥ 0, this greedy choice is correct — a node processed from the heap has its final shortest distance.

Min-heap invariant: heap stores (distance, node). Always process the smallest distance first. When we pop a node, if we've already found a shorter path to it, skip it (lazy deletion).
Dijkstra's — single source shortest path with min-heap
// graph[u] = [[v, weight], ...]
function dijkstra(graph, src, n) {
    const dist = new Array(n).fill(Infinity);
    dist[src] = 0;
    // Min-heap: [distance, node]
    // JS doesn't have built-in heap — use sorted array for small n,
    // or implement a binary heap for large n
    const heap = [[0, src]];

    while (heap.length) {
        // Extract minimum (simulate heap with sort for clarity)
        heap.sort((a, b) => a[0] - b[0]);
        const [d, u] = heap.shift();

        if (d > dist[u]) continue;  // stale entry (lazy deletion)

        for (const [v, w] of graph[u]) {
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                heap.push([dist[v], v]);
            }
        }
    }
    return dist;  // dist[i] = shortest distance from src to i
}

// For competitive programming — minimal heap implementation
class MinHeap {
    constructor() { this.h = []; }
    push(item) {
        this.h.push(item);
        let i = this.h.length - 1;
        while (i > 0) {
            const p = (i - 1) >> 1;
            if (this.h[p][0] <= this.h[i][0]) break;
            [this.h[p], this.h[i]] = [this.h[i], this.h[p]];
            i = p;
        }
    }
    pop() {
        const top = this.h[0];
        const last = this.h.pop();
        if (this.h.length) {
            this.h[0] = last;
            let i = 0;
            while (true) {
                let s = i, l = 2*i+1, r = 2*i+2;
                if (l < this.h.length && this.h[l][0] < this.h[s][0]) s = l;
                if (r < this.h.length && this.h[r][0] < this.h[s][0]) s = r;
                if (s === i) break;
                [this.h[s], this.h[i]] = [this.h[i], this.h[s]];
                i = s;
            }
        }
        return top;
    }
    get size() { return this.h.length; }
}

Bellman-Ford Algorithm

lightbulb
Bellman-Ford key insight: Relax ALL edges V-1 times. After k iterations, dist[v] = shortest path using at most k edges. Since any simple path has at most V-1 edges, V-1 iterations give the true shortest paths.

Negative cycle detection: Do one more (V-th) relaxation. If any distance still improves, a negative cycle exists (you can keep going around it to get shorter and shorter distances).
Bellman-Ford — handles negative weights
// edges = [[u, v, weight], ...]
function bellmanFord(edges, src, n) {
    const dist = new Array(n).fill(Infinity);
    dist[src] = 0;

    // Relax all edges n-1 times
    for (let i = 0; i < n - 1; i++) {
        let updated = false;
        for (const [u, v, w] of edges) {
            if (dist[u] !== Infinity && dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                updated = true;
            }
        }
        if (!updated) break;  // early exit if no update
    }

    // Check for negative cycles (V-th relaxation)
    for (const [u, v, w] of edges) {
        if (dist[u] !== Infinity && dist[u] + w < dist[v])
            return null;  // negative cycle detected
    }

    return dist;
}

Floyd-Warshall (All Pairs)

Floyd-Warshall — all pairs shortest path in O(V³)
function floydWarshall(n, edges) {
    // dist[i][j] = shortest distance from i to j
    const dist = Array.from({length: n}, (_, i) =>
        Array.from({length: n}, (_, j) => i === j ? 0 : Infinity)
    );
    for (const [u, v, w] of edges) {
        dist[u][v] = Math.min(dist[u][v], w);
        // For undirected: dist[v][u] = Math.min(dist[v][u], w);
    }

    // Try every intermediate node k
    for (let k = 0; k < n; k++)
        for (let i = 0; i < n; i++)
            for (let j = 0; j < n; j++)
                if (dist[i][k] + dist[k][j] < dist[i][j])
                    dist[i][j] = dist[i][k] + dist[k][j];

    // Negative cycle: dist[i][i] < 0 for any i
    return dist;
}

Worked Problems

brain
Shortest path algorithm selection:
- Unweighted graph → BFS (each edge = 1)
- Non-negative weights, single source → Dijkstra with min-heap
- Negative weights / negative cycle detection → Bellman-Ford
- At-most-k edges constraint → Bellman-Ford (k iterations)
- All pairs, small n (≤500) → Floyd-Warshall O(n³)
- Edge weights only 0/1 → 0-1 BFS with deque
- Minimax/maximin path (minimize the maximum edge) → Dijkstra with max instead of sum