Shortest Path

Shortest Path

Overview

Learn algorithms for finding the shortest path between two vertices in weighted graphs. This covers Dijkstra, Bellman-Ford, and Floyd-Warshall algorithms.


Table of Contents

  1. Shortest Path Algorithm Comparison
  2. Dijkstra
  3. Bellman-Ford
  4. Floyd-Warshall
  5. 0-1 BFS
  6. Practice Problems

1. Shortest Path Algorithm Comparison

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Algorithm       │ Time        │ Negative Wt   │ Use Case        │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ BFS             │ O(V+E)      │ āœ—             │ Unweighted      │
│ Dijkstra        │ O(E log V)  │ āœ—             │ Single source   │
│ Bellman-Ford    │ O(VE)       │ āœ“             │ Single source   │
│ Floyd-Warshall  │ O(V³)       │ āœ“             │ All pairs       │
│ 0-1 BFS         │ O(V+E)      │ 0,1 only      │ 0/1 weights     │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

2. Dijkstra

Concept

Shortest distance from single source to all vertices
Requirement: No negative weights

Principle:
1. Start distance = 0, rest = āˆž
2. Select unvisited vertex with minimum distance
3. Update distances to adjacent vertices through this vertex
4. Repeat until all vertices visited

Example

        (B)
       / 1 \
      4     2
     /       \
   (A)       (D)
     \       /
      2     1
       \ 3 /
        (C)

Start: A

Step  Visited    dist[A] dist[B] dist[C] dist[D]
----  -----      ------  ------  ------  ------
0     {}         0       āˆž       āˆž       āˆž
1     {A}        0       4       2       āˆž       ← Visit A, update B,C
2     {A,C}      0       4       2       5       ← Visit C, update D (2+3)
3     {A,C,B}    0       4       2       5       ← Visit B
4     {all}      0       4       2       5       ← Visit D

Result: A→B: 4, A→C: 2, A→D: 5

Implementation (Priority Queue)

// C - Adjacency list + array (simple version)
#define INF 1000000000
#define MAX_V 10001

typedef struct {
    int to, weight;
} Edge;

Edge adj[MAX_V][MAX_V];
int adjSize[MAX_V];
int dist[MAX_V];
int visited[MAX_V];
int V, E;

void dijkstra(int start) {
    for (int i = 0; i < V; i++) {
        dist[i] = INF;
        visited[i] = 0;
    }
    dist[start] = 0;

    for (int i = 0; i < V; i++) {
        // Find minimum distance vertex
        int u = -1;
        int minDist = INF;
        for (int j = 0; j < V; j++) {
            if (!visited[j] && dist[j] < minDist) {
                minDist = dist[j];
                u = j;
            }
        }

        if (u == -1) break;
        visited[u] = 1;

        // Update adjacent vertices
        for (int j = 0; j < adjSize[u]; j++) {
            int v = adj[u][j].to;
            int w = adj[u][j].weight;
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
            }
        }
    }
}
// C++ - Using priority queue (efficient)
#include <queue>
#include <vector>
using namespace std;

typedef pair<int, int> pii;  // {distance, vertex}

vector<int> dijkstra(const vector<vector<pii>>& adj, int start) {
    int V = adj.size();
    vector<int> dist(V, INT_MAX);
    priority_queue<pii, vector<pii>, greater<pii>> pq;

    dist[start] = 0;
    pq.push({0, start});

    while (!pq.empty()) {
        auto [d, u] = pq.top();
        pq.pop();

        // Skip if already processed
        if (d > dist[u]) continue;

        for (auto [v, weight] : adj[u]) {
            if (dist[u] + weight < dist[v]) {
                dist[v] = dist[u] + weight;
                pq.push({dist[v], v});
            }
        }
    }

    return dist;
}

// Usage
// adj[u].push_back({v, weight});  // u → v, weight
// auto dist = dijkstra(adj, 0);
# Python
import heapq
from collections import defaultdict

def dijkstra(graph, start):
    dist = {node: float('inf') for node in graph}
    dist[start] = 0
    pq = [(0, start)]  # (distance, vertex)

    while pq:
        d, u = heapq.heappop(pq)

        # Skip if already processed
        if d > dist[u]:
            continue

        for v, weight in graph[u]:
            if dist[u] + weight < dist[v]:
                dist[v] = dist[u] + weight
                heapq.heappush(pq, (dist[v], v))

    return dist

# Usage
# graph = defaultdict(list)
# graph[0].append((1, 4))  # 0 → 1, weight 4
# graph[0].append((2, 2))  # 0 → 2, weight 2
# dist = dijkstra(graph, 0)

Path Reconstruction

// C++
pair<vector<int>, vector<int>> dijkstraWithPath(
    const vector<vector<pii>>& adj, int start) {

    int V = adj.size();
    vector<int> dist(V, INT_MAX);
    vector<int> parent(V, -1);
    priority_queue<pii, vector<pii>, greater<pii>> pq;

    dist[start] = 0;
    pq.push({0, start});

    while (!pq.empty()) {
        auto [d, u] = pq.top();
        pq.pop();

        if (d > dist[u]) continue;

        for (auto [v, weight] : adj[u]) {
            if (dist[u] + weight < dist[v]) {
                dist[v] = dist[u] + weight;
                parent[v] = u;  // Store path
                pq.push({dist[v], v});
            }
        }
    }

    return {dist, parent};
}

// Print path
vector<int> getPath(const vector<int>& parent, int end) {
    vector<int> path;
    for (int v = end; v != -1; v = parent[v]) {
        path.push_back(v);
    }
    reverse(path.begin(), path.end());
    return path;
}
# Python
def dijkstra_with_path(graph, start):
    dist = {node: float('inf') for node in graph}
    parent = {node: None for node in graph}
    dist[start] = 0
    pq = [(0, start)]

    while pq:
        d, u = heapq.heappop(pq)

        if d > dist[u]:
            continue

        for v, weight in graph[u]:
            if dist[u] + weight < dist[v]:
                dist[v] = dist[u] + weight
                parent[v] = u
                heapq.heappush(pq, (dist[v], v))

    return dist, parent

def get_path(parent, end):
    path = []
    v = end
    while v is not None:
        path.append(v)
        v = parent[v]
    return path[::-1]

3. Bellman-Ford

Concept

Shortest distance from single source to all vertices
Feature: Allows negative weights, detects negative cycles

Principle:
1. Start distance = 0, rest = āˆž
2. Update distances through all edges (V-1 iterations)
3. If updated on Vth iteration, negative cycle exists

Example

        (B)
       / 1 \
      4     2
     /       \
   (A)       (D)
     \       /
      2     -5    ← negative weight
       \ 3 /
        (C)

Edges: (A,B,4), (A,C,2), (B,D,2), (C,B,1), (C,D,3)

Iteration 1: dist = [0, 4, 2, 5]
Iteration 2: dist = [0, 3, 2, 5]  ← B updated via C→B (2+1=3)
Iteration 3: dist = [0, 3, 2, 5]  ← no change

Result: A→B: 3, A→C: 2, A→D: 5

Implementation

// C
#define INF 1000000000

typedef struct {
    int from, to, weight;
} Edge;

Edge edges[20001];
int dist[501];
int V, E;

int bellmanFord(int start) {
    for (int i = 0; i < V; i++) {
        dist[i] = INF;
    }
    dist[start] = 0;

    // V-1 iterations
    for (int i = 0; i < V - 1; i++) {
        for (int j = 0; j < E; j++) {
            int u = edges[j].from;
            int v = edges[j].to;
            int w = edges[j].weight;

            if (dist[u] != INF && dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
            }
        }
    }

    // Check for negative cycle
    for (int j = 0; j < E; j++) {
        int u = edges[j].from;
        int v = edges[j].to;
        int w = edges[j].weight;

        if (dist[u] != INF && dist[u] + w < dist[v]) {
            return -1;  // Negative cycle exists
        }
    }

    return 0;
}
// C++
struct Edge {
    int from, to, weight;
};

vector<int> bellmanFord(int V, const vector<Edge>& edges, int start) {
    vector<int> dist(V, INT_MAX);
    dist[start] = 0;

    // V-1 iterations
    for (int i = 0; i < V - 1; i++) {
        for (const auto& e : edges) {
            if (dist[e.from] != INT_MAX &&
                dist[e.from] + e.weight < dist[e.to]) {
                dist[e.to] = dist[e.from] + e.weight;
            }
        }
    }

    // Check for negative cycle
    for (const auto& e : edges) {
        if (dist[e.from] != INT_MAX &&
            dist[e.from] + e.weight < dist[e.to]) {
            return {};  // Negative cycle
        }
    }

    return dist;
}
# Python
def bellman_ford(V, edges, start):
    dist = [float('inf')] * V
    dist[start] = 0

    # V-1 iterations
    for _ in range(V - 1):
        for u, v, w in edges:
            if dist[u] != float('inf') and dist[u] + w < dist[v]:
                dist[v] = dist[u] + w

    # Check for negative cycle
    for u, v, w in edges:
        if dist[u] != float('inf') and dist[u] + w < dist[v]:
            return None  # Negative cycle

    return dist

4. Floyd-Warshall

Concept

Shortest distance between all pairs of vertices
Feature: Allows negative weights, DP-based

Principle:
Path through k vs direct path
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

Repeat for all k

Example

     1     2
(0) ─→ (1) ─→ (2)
 └──────3──────→

Initial:
      0     1     2
  0 [ 0,    1,    3]
  1 [INF,   0,    2]
  2 [INF,  INF,   0]

k=1 (via 1):
  dist[0][2] = min(3, dist[0][1]+dist[1][2])
             = min(3, 1+2) = 3

Result: 0→2 shortest distance = 3

Implementation

// C
#define INF 1000000000
#define MAX_V 500

int dist[MAX_V][MAX_V];
int V;

void floydWarshall() {
    // k as intermediate vertex
    for (int k = 0; k < V; k++) {
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                if (dist[i][k] != INF && dist[k][j] != INF) {
                    if (dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
        }
    }
}

// Initialization
void initGraph() {
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            if (i == j) dist[i][j] = 0;
            else dist[i][j] = INF;
        }
    }
}
// C++
void floydWarshall(vector<vector<int>>& dist) {
    int V = dist.size();

    for (int k = 0; k < V; k++) {
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX) {
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
                }
            }
        }
    }
}

// Initialization
vector<vector<int>> initDist(int V) {
    vector<vector<int>> dist(V, vector<int>(V, INT_MAX));
    for (int i = 0; i < V; i++) {
        dist[i][i] = 0;
    }
    return dist;
}
# Python
def floyd_warshall(V, edges):
    INF = float('inf')

    # Initialization
    dist = [[INF] * V for _ in range(V)]
    for i in range(V):
        dist[i][i] = 0

    for u, v, w in edges:
        dist[u][v] = w

    # Floyd-Warshall
    for k in range(V):
        for i in range(V):
            for j in range(V):
                if dist[i][k] + dist[k][j] < dist[i][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]

    return dist

Path Reconstruction

// C++
void floydWarshallWithPath(vector<vector<int>>& dist,
                           vector<vector<int>>& next) {
    int V = dist.size();

    // Initialization
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            if (dist[i][j] != INT_MAX && i != j) {
                next[i][j] = j;
            } else {
                next[i][j] = -1;
            }
        }
    }

    for (int k = 0; k < V; k++) {
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX &&
                    dist[i][k] + dist[k][j] < dist[i][j]) {
                    dist[i][j] = dist[i][k] + dist[k][j];
                    next[i][j] = next[i][k];
                }
            }
        }
    }
}

vector<int> getPath(const vector<vector<int>>& next, int from, int to) {
    if (next[from][to] == -1) return {};

    vector<int> path = {from};
    while (from != to) {
        from = next[from][to];
        path.push_back(from);
    }
    return path;
}

5. 0-1 BFS

Concept

Shortest distance in graphs with edge weights 0 or 1
→ Solve in O(V+E) using Deque

Principle:
- Weight 0 edge: Add to front of deque
- Weight 1 edge: Add to back of deque

Effect: Minimum distance vertex always at front

Implementation

// C++
vector<int> zeroOneBFS(const vector<vector<pii>>& adj, int start) {
    int V = adj.size();
    vector<int> dist(V, INT_MAX);
    deque<int> dq;

    dist[start] = 0;
    dq.push_front(start);

    while (!dq.empty()) {
        int u = dq.front();
        dq.pop_front();

        for (auto [v, weight] : adj[u]) {
            if (dist[u] + weight < dist[v]) {
                dist[v] = dist[u] + weight;

                if (weight == 0) {
                    dq.push_front(v);
                } else {
                    dq.push_back(v);
                }
            }
        }
    }

    return dist;
}
# Python
from collections import deque

def zero_one_bfs(graph, start, n):
    dist = [float('inf')] * n
    dist[start] = 0
    dq = deque([start])

    while dq:
        u = dq.popleft()

        for v, weight in graph[u]:
            if dist[u] + weight < dist[v]:
                dist[v] = dist[u] + weight

                if weight == 0:
                    dq.appendleft(v)
                else:
                    dq.append(v)

    return dist

6. Practice Problems

Problem 1: Find Cities at Exact Distance K

Find all cities exactly K distance from start city.

Solution Code
import heapq

def cities_at_distance_k(n, edges, start, k):
    graph = [[] for _ in range(n + 1)]
    for a, b in edges:
        graph[a].append(b)  # Directed

    dist = [float('inf')] * (n + 1)
    dist[start] = 0
    pq = [(0, start)]

    while pq:
        d, u = heapq.heappop(pq)
        if d > dist[u]:
            continue
        for v in graph[u]:
            if dist[u] + 1 < dist[v]:
                dist[v] = dist[u] + 1
                heapq.heappush(pq, (dist[v], v))

    result = [i for i in range(1, n + 1) if dist[i] == k]
    return sorted(result) if result else [-1]

Problem 2: Negative Cycle Detection

Check if a negative cycle exists.

Solution Code
def has_negative_cycle(V, edges):
    dist = [0] * V  # Can start from any vertex

    for _ in range(V - 1):
        for u, v, w in edges:
            if dist[u] + w < dist[v]:
                dist[v] = dist[u] + w

    for u, v, w in edges:
        if dist[u] + w < dist[v]:
            return True

    return False
Difficulty Problem Platform Algorithm
⭐⭐ Shortest Path BOJ Dijkstra
⭐⭐ Network Delay Time LeetCode Dijkstra
⭐⭐⭐ Time Machine BOJ Bellman-Ford
⭐⭐⭐ Floyd BOJ Floyd-Warshall
⭐⭐⭐ Find Path BOJ Floyd-Warshall
⭐⭐⭐⭐ Cheapest Flights LeetCode Modified Dijkstra

Algorithm Selection Guide

Question 1: Are there weights?
ā”œā”€ā”€ No → BFS (O(V+E))
└── Yes ↓

Question 2: Are there negative weights?
ā”œā”€ā”€ No → Dijkstra (O(E log V))
└── Yes ↓

Question 3: Need negative cycle detection?
ā”œā”€ā”€ Yes → Bellman-Ford (O(VE))
└── No ↓

Question 4: Need all-pairs shortest distance?
ā”œā”€ā”€ Yes → Floyd-Warshall (O(V³))
└── No → Bellman-Ford (O(VE))

Additional: If weights are only 0 and 1 → 0-1 BFS (O(V+E))

Next Steps


References

to navigate between lessons