⚡ dixl.eu DEV LEXICON
● Spec v2026.1 RFC / MDN / POSIX

Johnson’s Algorithm

✍️ Spec Review: Dixl Architecture Committee • 📅 Updated: October 24, 2026 • ⏱️ Reading: ~5 min
Johnson’s Algorithm - Featured Image

Johnson's algorithm solves the all-pairs shortest path problem for sparse directed graphs with arbitrary edge weights, provided no negative cycles exist. By combining the Bellman-Ford algorithm to reweight edges and eliminate negative weights with Dijkstra's algorithm utilizing Fibonacci heaps for each vertex, Johnson's algorithm achieves a superior asymptotic time complexity compared to running Bellman-Ford from every vertex, making it the optimal choice for sparse graphs.

🛠️

HOW-TO IMPLEMENT & CONFIGURE

📋 Prerequisites & Environment:

  • Python 3.10+ runtime environment
  • Standard library modules: heapq
  • Understanding of directed graph representations using adjacency lists
  • Familiarity with Bellman-Ford and Dijkstra single-source shortest path primitives
STEP 1

Architectural Setup & Environment Initialization

Define the graph data structures using an adjacency list where each vertex maps to a dictionary of target vertices and edge weights. Ensure support for adding a fictitious node to guarantee the convergence of potential functions.

from typing import Dict, List, Tuple
import heapq

Graph = Dict[int, Dict[int, float]]
STEP 2

Core Algorithm Implementation

Implement Johnson's algorithm by augmenting the graph with an artificial vertex s, running Bellman-Ford from s to compute potentials h(v), reweighting edges to non-negative values using w_hat(u, v) = w(u, v) + h(u) – h(v), and executing Dijkstra's algorithm for every vertex.

def bellman_ford(vertices: List[int], edges: List[Tuple[int, int, float]], source: int) -> Tuple[bool, Dict[int, float]]:
    distance = {v: float('inf') for v in vertices}
    distance[source] = 0
    for _ in range(len(vertices) - 1):
        updated = False
        for u, v, w in edges:
            if distance[u] != float('inf') and distance[u] + w < distance[v]:
                distance[v] = distance[u] + w
                updated = True
        if not updated:
            break
    for u, v, w in edges:
        if distance[u] != float('inf') and distance[u] + w < distance[v]:
            return False, {}  # Negative cycle detected
    return True, distance

def dijkstra(graph: Graph, source: int) -> Dict[int, float]:
    distances = {v: float('inf') for v in graph}
    distances[source] = 0
    pq = [(0.0, source)]
    while pq:
        current_dist, u = heapq.heappop(pq)
        if current_dist > distances[u]:
            continue
        for v, weight in graph[u].items():
            distance = current_dist + weight
            if distance < distances[v]:
                distances[v] = distance
                heapq.heappush(pq, (distance, v))
    return distances

def johnsons_algorithm(graph: Graph) -> Dict[int, Dict[int, float]]:
    vertices = list(graph.keys())
    s = object()
    augmented_graph = {v: dict(d) for v, d in graph.items()}
    augmented_graph[s] = {v: 0.0 for v in vertices}
    
    edges = []
    for u in augmented_graph:
        for v, w in augmented_graph[u].items():
            edges.append((u, v, w))
            
    success, h = bellman_ford(list(augmented_graph.keys()), edges, s)
    if not success:
        raise ValueError("Graph contains a negative-weight cycle")
        
    reweighted_graph: Graph = {}
    for u in graph:
        reweighted_graph[u] = {}
        for v, w in graph[u].items():
            reweighted_graph[u][v] = w + h[u] - h[v]
            
    all_pairs_shortest_paths = {}
    for u in vertices:
        d_hat = dijkstra(reweighted_graph, u)
        all_pairs_shortest_paths[u] = {}
        for v in vertices:
            if d_hat[v] != float('inf'):
                all_pairs_shortest_paths[u][v] = d_hat[v] - h[u] + h[v]
            else:
                all_pairs_shortest_paths[u][v] = float('inf')
                
    return all_pairs_shortest_paths
STEP 3

Verification, Profiling & Unit Testing

Construct automated unit tests to validate correctness against known reference outputs, checking both sparse network topologies and edge cases involving negative weights without cycles.

def test_johnsons():
    graph = {
        0: {1: -5, 2: 2, 3: 3},
        1: {2: 4},
        2: {3: 1},
        3: {}
    }
    paths = johnsons_algorithm(graph)
    assert paths[0][3] == -1
    print("All assertions passed successfully.")

if __name__ == '__main__':
    test_johnsons()
STEP 4

Production Hardening & Common Failure Modes

Guard against unhandled negative cycles by wrapping the potential calculation in robust exception handling. Ensure memory ceilings are respected by employing generator expressions or memory-mapped storage for dense matrix outputs if V exceeds 10,000 nodes.

# Production hardening context: Verify graph type integrity and prevent floating-point underflow

Mathematical Formulation & Reweighting Mechanics

Johnson's algorithm addresses the limitation of Dijkstra's algorithm, which fails in the presence of negative edge weights, and the inefficiency of running Bellman-Ford O(V) times (yielding O(V^2 * E)). The core mathematical insight is reweighting the edges to make them non-negative while preserving the shortest paths. Given a graph G = (V, E) with weight function w: E -> R, we add a new vertex s with zero-weight directed edges to all vertices in V. We compute shortest path estimates h(v) = delta(s, v) using Bellman-Ford. If a negative cycle exists, Bellman-Ford detects it and halts. Otherwise, we define the new weight function w_hat(u, v) = w(u, v) + h(u) – h(v). For any path from u to v, the total weight under w_hat telescopes, differing from the original weight by a constant offset h(u) – h(v), ensuring shortest paths remain unchanged.

Asymptotic Complexity Analysis

The time complexity is dominated by two phases: 1) Executing the Bellman-Ford algorithm once on the augmented graph, taking O(V * E) time. 2) Running Dijkstra's algorithm from each of the V vertices. Using a binary heap, each Dijkstra execution takes O(E * log V), resulting in O(V * E * log V). Utilizing Fibonacci heaps reduces the Dijkstra phase complexity to O(V^2 * log V + V * E). The space complexity is O(V^2 + E) to store the original graph structure, potentials, and the resulting all-pairs shortest path distance matrix.

📚 STANDARDS & ACADEMIC RFC CITATIONS

  • Johnson, Donald B. (1977). 'Efficient algorithms for shortest paths in sparse networks'. Journal of the ACM, 24(1), 1-13.
  • Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L.; Stein, Clifford (2009). Introduction to Algorithms (3rd ed.). MIT Press.