Jump to content

Johnson's algorithm

From Rosetta Code
Task
Johnson's algorithm
You are encouraged to solve this task according to the task description, using any language you may know.
Johnson's Algorithm

Johnson's algorithm is a procedure for finding the shortest paths between all pairs of vertices in a sparse, edge-weighted, directed graph. It combines elements of the Bellman-Ford algorithm and Dijkstra's algorithm to achieve better performance than the Floyd-Warshall algorithm for sparse graphs.

Problem Description
Input
  • A directed graph where is the set of vertices and is the set of edges
  • A weight function that assigns a real-valued weight to each edge
  • The graph may contain negative-weight edges but must not contain any negative-weight cycles
Output
  • A matrix where contains the weight of the shortest path from vertex to vertex
  • If no path exists from to ,
Constraints
  • The graph must not contain any negative-weight cycles
  • Time complexity:
  • Space complexity:
Key Insight

The key insight of Johnson's algorithm is the use of a reweighting technique to transform all edge weights to be non-negative, while preserving shortest path relationships. This allows the use of Dijkstra's algorithm (which requires non-negative edge weights) for each vertex as a source.

Applications

Johnson's algorithm is particularly useful in:

  • Sparse graphs where is much smaller than
  • Network routing optimization
  • Traffic flow analysis
  • Resource allocation problems
  • Any context requiring all-pairs shortest paths in a sparse graph with potential negative edges


C++

#include <cstdint>
#include <iostream>
#include <limits>
#include <map>
#include <optional>
#include <queue>
#include <vector>

constexpr double INF{std::numeric_limits<double>::max()};

struct Edge {
	uint32_t u;
	uint32_t v;
	double weight;
};

struct Vertex_and_Weight {
	uint32_t vertex;
	double weight;
};

/**
 * Return a list of shortest path distances from the source vertex in the original graph to all other vertices
 */
std::vector<double> dijkstra_algorithm(
		const uint32_t& vertex_count,
		std::map<uint32_t, std::vector<Vertex_and_Weight>>& reweighted_adjacencies,
		const uint32_t& source_vertex,
		const std::vector<double>& values) {

	std::vector<double> distances(vertex_count, INF);
	distances[source_vertex] = 0.0;

	auto compare = [](const Vertex_and_Weight& a, const Vertex_and_Weight& b) { return a.weight > b.weight; };

	std::priority_queue<Vertex_and_Weight, std::vector<Vertex_and_Weight>, decltype(compare)> priority_queue;
	priority_queue.emplace(Vertex_and_Weight(source_vertex, 0.0));

	std::vector<double> final_distances(vertex_count, INF);

	while ( ! priority_queue.empty() ) {
		Vertex_and_Weight vertex_and_Weight = priority_queue.top();
		priority_queue.pop();
		const uint32_t vertex = vertex_and_Weight.vertex;
		if ( vertex_and_Weight.weight > distances[vertex] ) {
			continue;
		}

		// Store the final shortest path distance, translated back to the distance in the original graph
		// which prevents processing vertices disconnected from the source vertex
		if ( final_distances[vertex] == INF ) {
			 if ( distances[vertex] == INF ) { // This should not happen, but is included as a safety check
				 final_distances[vertex] = INF;
			 } else {
				 // Translate distance back to its original weight: d(u,v) = d'(u,v) - h[u] + h[v]
				 final_distances[vertex] = distances[vertex] - values[source_vertex] + values[vertex];
			 }
		}

		// Relax the edges outgoing from vertex
		if ( reweighted_adjacencies.contains(vertex) ) {
			for ( Vertex_and_Weight pair : reweighted_adjacencies[vertex] ) {
				if ( distances[vertex] != INF
					&& distances[vertex] + pair.weight < distances[pair.vertex] ) {
					distances[pair.vertex] = distances[vertex] + pair.weight;
					priority_queue.emplace(Vertex_and_Weight(pair.vertex, distances[pair.vertex]));
				}
			}
		}
	}

	// Translate distance back to its original weight for any remaining reachable vertices
	// This handles cases where a vertex was reachable, but was not the minimum vertex
	// removed from the priority queue when its final distance was determined.
	for ( uint32_t i = 0; i < vertex_count; ++i ) {
		 if ( final_distances[i] == INF && distances[i] != INF ) {
			 final_distances[i] = distances[i] - values[source_vertex] + values[i];
		 }
	}

	return final_distances;
}

/**
 * Return a list of shortest distances from the source vertex to all other vertices,
 * or an empty optional if a negative cycle is detected
 */
std::optional<std::vector<double>> bellman_ford_algorithm(
		const uint32_t& augmented_vertex_count, const std::vector<Edge>& edges, const uint32_t& source_vertex) {
	std::vector<double> distances(augmented_vertex_count, INF);
	distances[source_vertex] = 0.0;

	// Relax the edges (augmentedVertexCount - 1) times
	bool updated = true;
	for ( uint32_t i = 0; i < augmented_vertex_count - 1 && updated; ++i ) {
		updated = false;
		for ( uint32_t j = 0; j < edges.size(); ++j ) {
			Edge edge = edges[j];
			if ( distances[edge.u] != INF && distances[edge.u] + edge.weight < distances[edge.v] ) {
				distances[edge.v] = distances[edge.u] + edge.weight;
				updated = true;
			}
		}
	}

	// Check for negative cycles in the graph
	for ( const Edge& edge : edges ) {
		if ( distances[edge.u] != INF && distances[edge.u] + edge.weight < distances[edge.v] ) {
			return std::nullopt; // Indicates to the calling method that a negative cycle has been detected
		}
	}

	return distances;
}

/**
 * Return the shortest path between all pairs of vertices in an edge weighted directed graph
 * For a full description of the algorithm visit https://en.wikipedia.org/wiki/Johnson%27s_algorithm
 */
std::optional<std::vector<std::vector<double>>> johnsons_algorithm(const std::vector<std::vector<double>>& graph) {
	const uint32_t vertex_count = graph.size();
	std::vector<Edge> original_edges;

	// Step 0: Build a list of edges for the original graph
	for ( uint32_t i = 0; i < vertex_count; ++i ) {
		for ( uint32_t j = 0; j < vertex_count; ++j ) {
			const double weight = graph[i][j];
			if ( i == j ) {
				if ( weight != 0.0 ) {
					std::cout << "Warning: graph[i][i] for i = " << i << " is " << weight
							  << ", expected to be 0.0, resetting it to 0.0" << std::endl;
				}
			} else if ( weight != INF ) {
				original_edges.emplace_back(Edge(i, j, weight));
			}
		}
	}

	// Step 1: Form the augmented graph
	// Add a new vertex with index 'vertex_count' and having 0-weight edges to all the original vertices
	std::vector<Edge> augmented_edges = original_edges;
	for ( uint32_t i = 0; i < vertex_count; ++i ) {
		augmented_edges.emplace_back(Edge(vertex_count, i, 0.0));
	}

	// Step 2: Invoke the Bellman-Ford Algorithm starting from the new vertex
	std::optional<std::vector<double>> h_values = 
        bellman_ford_algorithm(vertex_count + 1, augmented_edges, vertex_count);

	if ( ! h_values ) {
		return std::nullopt; // A negative cycle was detected by the Bellman-Ford Algorithm
	}

	std::vector<double> values = h_values.value();
	values.pop_back(); // Remove the value for the augmented vertex

	// Step 3: Reweight the edges
	std::map<uint32_t, std::vector<Vertex_and_Weight>> reweighted_adjacencies;

	for ( const Edge& edge : original_edges ) {
		// Ensure the 'values' are valid before reweighting
		if ( values[edge.u] == INF || values[edge.v] == INF ) {
			// This can happen if the original graph was not strongly connected to the augmented vertex.
			// While not strictly an error for Johnson's Algorithm, because paths might still exist between
			// reachable nodes, it means the reweighting might involve INF.
			// Computation can proceed since Dijkstra's Algorithm can handle INF.
			std::cout << "Warning: invalid hValues detected by the Bellman-Ford Algorithm." << std::endl;
		}

		const double reweight = edge.weight + values[edge.u] - values[edge.v];
		reweighted_adjacencies[edge.u].emplace_back(Vertex_and_Weight(edge.v, reweight));
	}

	// Step 4: Invoke Dijkstra's Algorithm starting from each vertex on the reweighted graph
	std::vector<std::vector<double>> all_pairs_shortest_paths;
	for ( uint32_t u = 0; u < vertex_count; ++u ) {
		all_pairs_shortest_paths.emplace_back(dijkstra_algorithm(vertex_count, reweighted_adjacencies, u, values));
	}

	// Step 5: Return the result matrix
	return all_pairs_shortest_paths;
}

int main() {
	// The element (i, j) is the weight of the edge from vertex i to vertex j.
	// INF, for infinity, means that there is no edge from vertex i to vertex j.
	const std::vector<std::vector<double>> graph = {
		{ 0.0, -5.0, 2.0, 3.0 },
		{ INF,  0.0, 4.0, INF },
		{ INF,  INF, 0.0, 1.0 },
		{ INF,  INF, INF, 0.0 } };

	const std::optional<std::vector<std::vector<double>>> result = johnsons_algorithm(graph);

	if ( result ) {
		std::cout << "All pairs shortest paths:" << std::endl;
		std::cout << "The element (i, j) is the shortest path between vertex i and vertex j." << std::endl;
		for ( const std::vector<double>& row : result.value() ) {
			std::cout << "[";
			for ( const double& number : row ) {
				if ( number == INF ) {
					std::cout << "INF ";
				} else {
					std::cout << number << " ";
				}
			}
			std::cout << "]" << std::endl;
		}
	} else {
		std::cout << "A negative cycle was detected in the graph." << std::endl;
	}
}
Output:
All pairs shortest paths:
The element (i, j) is the shortest path between vertex i and vertex j.
[0 -5 -1 0 ]
[INF 0 4 5 ]
[INF INF 0 1 ]
[INF INF INF 0 ]

FreeBASIC

Translation of: Python
Const INF As Integer = &H7FFFFFFF

Type Edge
    u As Integer
    v As Integer
    w As Integer
End Type

Function BellmanFord(num_vertices As Integer, edges() As Edge, source As Integer, dist() As Integer) As Boolean
    Dim As Integer i, j, updated
    
    For i = 0 To num_vertices - 1
        dist(i) = INF
    Next
    dist(source) = 0
    
    ' Relax edges V-1 times
    For i = 1 To num_vertices - 1
        updated = 0
        For j = 0 To Ubound(edges)
            With edges(j)
                If dist(.u) <> INF Andalso dist(.u) + .w < dist(.v) Then
                    dist(.v) = dist(.u) + .w
                    updated = 1
                End If
            End With
        Next
        ' If no update in a full pass, we can stop early
        If updated = 0 Then Exit For
    Next
    
    ' Check for negative cycles
    For j = 0 To Ubound(edges)
        With edges(j)
            If dist(.u) <> INF Andalso dist(.u) + .w < dist(.v) Then 
                Print "Graph contains a negative weight cycle"
                Return False
            End If
        End With
    Next
    Return True
End Function

Sub Dijkstra(num_vertices As Integer, adj() As Edge, source As Integer, h() As Integer, result() As Integer, row As Integer)
    Dim As Integer visited(num_vertices - 1), dist(num_vertices - 1)
    Dim As Integer i, min_dist, u, cnt
    
    For i = 0 To num_vertices - 1
        dist(i) = INF
        visited(i) = 0
    Next
    dist(source) = 0
    
    For cnt = 0 To num_vertices - 1
        min_dist = INF
        For i = 0 To num_vertices - 1
            If visited(i) = 0 Andalso dist(i) < min_dist Then
                min_dist = dist(i)
                u = i
            End If
        Next
        
        If min_dist = INF Then Exit For
        visited(u) = 1
        
        For i = 0 To Ubound(adj)
            With adj(i)
                If .u = u Then
                    If dist(u) <> INF Andalso dist(u) + .w < dist(.v) Then dist(.v) = dist(u) + .w
                End If
            End With
        Next
    Next
    
    For i = 0 To num_vertices - 1
        result(row, i) = Iif(dist(i) <> INF, dist(i) - h(source) + h(i), INF)
    Next
End Sub

Sub JohnsonsAlgorithm(graph() As Integer, result() As Integer)
    Dim As Integer V = Ubound(graph, 1) + 1
    Dim As Edge edges(), augmented_edges()
    Dim As Integer h(V)
    Dim As Integer i, j, k
    
    ' --- Step 0: Handle Input and Build Edge List for Original Graph ---
    k = 0
    Redim edges(-1)
    For i = 0 To V - 1
        For j = 0 To V - 1
            If i <> j Andalso graph(i, j) <> 0 Then
                Redim Preserve edges(k)
                edges(k).u = i
                edges(k).v = j
                edges(k).w = graph(i, j)
                k += 1
            End If
        Next
    Next
    
    ' --- Step 1: Form the augmented graph G' ---
    Redim augmented_edges(Ubound(edges) + V)
    For i = 0 To Ubound(edges)
        augmented_edges(i) = edges(i)
    Next
    For i = 0 To V - 1
        augmented_edges(Ubound(edges) + 1 + i).u = V
        augmented_edges(Ubound(edges) + 1 + i).v = i
        augmented_edges(Ubound(edges) + 1 + i).w = 0
    Next
    
    ' --- Step 2: Run Bellman-Ford from the new source 's' ---
    If Not BellmanFord(V + 1, augmented_edges(), V, h()) Then
        Print "Negative cycle detected in the graph."
        Exit Sub
    End If
    
    ' --- Step 3: Reweight the edges ---
    For i = 0 To Ubound(edges)
        edges(i).w += h(edges(i).u) - h(edges(i).v)
    Next
    
    ' --- Step 4: Run Dijkstra from each vertex on the reweighted graph ---
    Redim result(V - 1, V - 1)
    For i = 0 To V - 1
        Dijkstra(V, edges(), i, h(), result(), i)
    Next
End Sub

' --- Test Case ---
Dim graph(3, 3) As Integer = { _
{0, -5,  2,  3}, _
{0,  0,  4,  0}, _ ' Assuming 0 means no edge from 1->0 and 1->3
{0,  0,  0,  1}, _ ' Assuming 0 means no edge from 2->0 and 2->1
{0,  0,  0,  0} }  ' Assuming 0 means no edge from 3->0, 3->1, 3->2

Dim As Integer result(3, 3)

JohnsonsAlgorithm(graph(), result())

Print "All-pairs shortest paths:"
For i As Integer = 0 To 3
    For j As Integer = 0 To 3
        If result(i, j) = INF Then
            Print "INF ";
        Else
            Print Using "##  "; result(i, j);
        End If
    Next
    Print
Next

Sleep
Output:
All-pairs shortest paths:
 0  -5  -1   0
INF  0   4   5
INF INF  0   1
INF INF INF  0

Go

Works with: Go version 1.10.2
Translation of: Java
package main

import (
	"container/heap"
	"fmt"
	"math"
)

const INF = math.MaxFloat64

type Edge struct {
	u, v   int
	weight float64
}

type VertexAndWeight struct {
	v      int
	weight float64
}

type WeightAndVertex struct {
	weight float64
	vertex int
}

// Priority queue implementation for Dijkstra's algorithm
type PriorityQueue []WeightAndVertex

func (pq PriorityQueue) Len() int           { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool { return pq[i].weight < pq[j].weight }
func (pq PriorityQueue) Swap(i, j int)      { pq[i], pq[j] = pq[j], pq[i] }

func (pq *PriorityQueue) Push(x interface{}) {
	*pq = append(*pq, x.(WeightAndVertex))
}

func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	item := old[n-1]
	*pq = old[0 : n-1]
	return item
}

func main() {
	// The element (i, j) is the weight of the edge from vertex i to vertex j.
	// INF, for infinity, means that there is no edge from vertex i to vertex j.
	graph := [][]float64{
		{0.0, -5.0, 2.0, 3.0},
		{INF, 0.0, 4.0, INF},
		{INF, INF, 0.0, 1.0},
		{INF, INF, INF, 0.0},
	}

	result, hasNegativeCycle := johnsonsAlgorithm(graph)

	if !hasNegativeCycle {
		fmt.Println("All pairs shortest paths:")
		fmt.Println("The element (i, j) is the shortest path between vertex i and vertex j.")
		for _, row := range result {
			fmt.Print("[")
			for _, number := range row {
				if number == INF {
					fmt.Print("INF ")
				} else {
					fmt.Printf("%v ", number)
				}
			}
			fmt.Println("]")
		}
	} else {
		fmt.Println("A negative cycle was detected in the graph.")
	}
}

// johnsonsAlgorithm returns the shortest path between all pairs of vertices in an edge weighted directed graph
// and a boolean indicating whether a negative cycle was detected
func johnsonsAlgorithm(graph [][]float64) ([][]float64, bool) {
	vertexCount := len(graph)
	originalEdges := []Edge{}

	// Step 0: Build a list of edges for the original graph
	for i := 0; i < vertexCount; i++ {
		for j := 0; j < vertexCount; j++ {
			weight := graph[i][j]
			if i == j {
				if weight != 0.0 {
					fmt.Printf("Warning: graph[i][i] for i = %d is %v, expected to be 0.0, resetting it to 0.0\n", i, weight)
				}
			} else if weight != INF {
				originalEdges = append(originalEdges, Edge{i, j, weight})
			}
		}
	}

	// Step 1: Form the augmented graph
	// Add a new vertex with index 'vertexCount' and having 0-weight edges to all the original vertices
	augmentedEdges := make([]Edge, len(originalEdges))
	copy(augmentedEdges, originalEdges)
	for i := 0; i < vertexCount; i++ {
		augmentedEdges = append(augmentedEdges, Edge{vertexCount, i, 0.0})
	}

	// Step 2: Invoke the Bellman-Ford Algorithm starting from the new vertex
	hValues, hasNegativeCycle := bellmanFordAlgorithm(vertexCount+1, augmentedEdges, vertexCount)

	if hasNegativeCycle {
		return nil, true // A negative cycle was detected by the Bellman-Ford Algorithm
	}

	values := hValues[:vertexCount] // Remove the value for the augmented vertex

	// Step 3: Reweight the edges
	reweightedAdjacencies := make(map[int][]VertexAndWeight)
	for v := 0; v < vertexCount; v++ {
		reweightedAdjacencies[v] = []VertexAndWeight{}
	}

	for _, edge := range originalEdges {
		// Ensure the 'values' are valid before reweighting
		if values[edge.u] == INF || values[edge.v] == INF {
			// This can happen if the original graph was not strongly connected to the augmented vertex.
			// While not strictly an error for Johnson's Algorithm, because paths might still exist between
			// reachable nodes, it means the reweighting might involve INF.
			// Computation can proceed since Dijkstra's Algorithm can handle INF.
			fmt.Println("Warning: invalid hValues detected by the Bellman-Ford Algorithm.")
		}

		reweight := edge.weight + values[edge.u] - values[edge.v]
		reweightedAdjacencies[edge.u] = append(reweightedAdjacencies[edge.u], VertexAndWeight{edge.v, reweight})
	}

	// Step 4: Invoke Dijkstra's Algorithm starting from each vertex on the reweighted graph
	allPairsShortestPaths := make([][]float64, vertexCount)
	for u := 0; u < vertexCount; u++ {
		allPairsShortestPaths[u] = dijkstraAlgorithm(vertexCount, reweightedAdjacencies, u, values)
	}

	// Step 5: Return the result matrix
	return allPairsShortestPaths, false
}

// bellmanFordAlgorithm returns a list of shortest distances from the source vertex to all other vertices,
// and a boolean indicating whether a negative cycle was detected
func bellmanFordAlgorithm(augmentedVertexCount int, edges []Edge, sourceVertex int) ([]float64, bool) {
	distances := make([]float64, augmentedVertexCount)
	for i := range distances {
		distances[i] = INF
	}
	distances[sourceVertex] = 0.0

	// Relax the edges (augmentedVertexCount - 1) times
	updated := true
	for i := 0; i < augmentedVertexCount-1 && updated; i++ {
		updated = false
		for j := 0; j < len(edges); j++ {
			edge := edges[j]
			if distances[edge.u] != INF && distances[edge.u]+edge.weight < distances[edge.v] {
				distances[edge.v] = distances[edge.u] + edge.weight
				updated = true
			}
		}
	}

	// Check for negative cycles in the graph
	for _, edge := range edges {
		if distances[edge.u] != INF && distances[edge.u]+edge.weight < distances[edge.v] {
			return nil, true // Indicates to the calling method that a negative cycle has been detected
		}
	}

	return distances, false
}

// dijkstraAlgorithm returns a list of shortest path distances from the source vertex in the original graph to all other vertices
func dijkstraAlgorithm(vertexCount int, reweightedAdjacencies map[int][]VertexAndWeight, sourceVertex int, values []float64) []float64 {
	distances := make([]float64, vertexCount)
	for i := range distances {
		distances[i] = INF
	}
	distances[sourceVertex] = 0.0

	pq := make(PriorityQueue, 0)
	heap.Init(&pq)
	heap.Push(&pq, WeightAndVertex{0.0, sourceVertex})

	finalDistances := make([]float64, vertexCount)
	for i := range finalDistances {
		finalDistances[i] = INF
	}

	for pq.Len() > 0 {
		weightAndVertex := heap.Pop(&pq).(WeightAndVertex)
		vertex := weightAndVertex.vertex
		if weightAndVertex.weight > distances[vertex] {
			continue
		}

		// Store the final shortest path distance, translated back to the distance in the original graph
		// which prevents processing vertices disconnected from the source vertex
		if finalDistances[vertex] == INF {
			if distances[vertex] == INF { // This should not happen, but is included as a safety check
				finalDistances[vertex] = INF
			} else {
				// Translate distance back to its original weight: d(u,v) = d'(u,v) - h[u] + h[v]
				finalDistances[vertex] = distances[vertex] - values[sourceVertex] + values[vertex]
			}
		}

		// Relax the edges outgoing from vertex
		if adjList, exists := reweightedAdjacencies[vertex]; exists {
			for _, pair := range adjList {
				if distances[vertex] != INF && distances[vertex]+pair.weight < distances[pair.v] {
					distances[pair.v] = distances[vertex] + pair.weight
					heap.Push(&pq, WeightAndVertex{distances[pair.v], pair.v})
				}
			}
		}
	}

	// Translate distance back to its original weight for any remaining reachable vertices
	// This handles cases where a vertex was reachable, but was not the minimum vertex
	// removed from the priority queue when its final distance was determined.
	for i := 0; i < vertexCount; i++ {
		if finalDistances[i] == INF && distances[i] != INF {
			finalDistances[i] = distances[i] - values[sourceVertex] + values[i]
		}
	}

	return finalDistances
}
Output:
All pairs shortest paths:
The element (i, j) is the shortest path between vertex i and vertex j.
[0 -5 -1 0 ]
[INF 0 4 5 ]
[INF INF 0 1 ]
[INF INF INF 0 ]



Java

import java.util.AbstractQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.PriorityQueue;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public final class JohnsonsAlgorithm {

	public static void main(String[] args) {
		// The element (i, j) is the weight of the edge from vertex i to vertex j.
		// INF, for infinity, means that there is no edge from vertex i to vertex j.
		List<List<Double>> graph = List.of(
				List.of( 0.0, -5.0, 2.0, 3.0 ),
			    List.of( INF,  0.0, 4.0, INF ),
			    List.of( INF,  INF, 0.0, 1.0 ),
			    List.of( INF,  INF, INF, 0.0 ) );

		Optional<List<List<Double>>> result = johnsonsAlgorithm(graph);
		
		if ( result.isPresent() ) {
		    System.out.println("All pairs shortest paths:");
		    System.out.println("The element (i, j) is the shortest path between vertex i and vertex j.");
		    for ( List<Double> row : result.get() ) {
		    	System.out.print("[");
		    	for ( double number : row ) {
		    		System.out.print(( number == INF ) ? "INF " : number + " ");
		    	}
		    	System.out.println("]");
		    }
		} else {
		    System.out.println("A negative cycle was detected in the graph.");
		}
	}
	
	/**
	 * Return the shortest path between all pairs of vertices in an edge weighted directed graph
	 * For a full description of the algorithm visit https://en.wikipedia.org/wiki/Johnson%27s_algorithm
	 */
	private static Optional<List<List<Double>>> johnsonsAlgorithm(List<List<Double>> graph) {
		final int vertexCount = graph.size();
	    List<Edge> originalEdges = new ArrayList<Edge>();

	    // Step 0: Build a list of edges for the original graph
	    IntStream.range(0, vertexCount).forEach( i -> {
	        IntStream.range(0, vertexCount).forEach( j -> {
	        	final double weight = graph.get(i).get(j);
	            if ( i == j ) {
	                if ( weight != 0.0 ) {
	                    System.out.println("Warning: graph[i][i] for i = " + i + " is " + weight
	                    		           + ", expected to be 0.0, resetting it to 0.0");
	                }
	            } else if ( weight != INF ) {
	                originalEdges.addLast( new Edge(i, j, weight) );
	            }
	        } );
	    } );
	    
	    // Step 1: Form the augmented graph
	    // Add a new vertex with index 'vertexCount' and having 0-weight edges to all the original vertices
	    List<Edge> augmentedEdges = Stream.concat(originalEdges.stream(),
	    	Stream.iterate(0, i -> i + 1 ).limit(vertexCount).map( i -> new Edge(vertexCount, i, 0.0) )).toList();
	    
	    // Step 2: Invoke the Bellman-Ford Algorithm starting from the new vertex
	    Optional<List<Double>> hValues = bellmanFordAlgorithm(vertexCount + 1, augmentedEdges, vertexCount);
	    
	    if ( hValues.isEmpty() ) {	        
	        return Optional.empty(); // A negative cycle was detected by the Bellman-Ford Algorithm
	    }
	    
	    List<Double> values = hValues.get();
	    values.removeLast(); // Remove the value for the augmented vertex
	    
	    // Step 3: Reweight the edges
	    Map<Integer, List<VertexAndWeight>> reweightedAdjacencies = IntStream.range(0, vertexCount).boxed()
	    	.collect(Collectors.toMap(Function.identity(), v -> new ArrayList<VertexAndWeight>() ));
	    
	    originalEdges.stream().forEach( edge -> {
	        // Ensure the 'values' are valid before reweighting
	        if ( values.get(edge.u) == INF || values.get(edge.v) == INF ) {
	            // This can happen if the original graph was not strongly connected to the augmented vertex. 
	        	// While not strictly an error for Johnson's Algorithm, because paths might still exist between
	        	// reachable nodes, it means the reweighting might involve INF.
	            // Computation can proceed since Dijkstra's Algorithm can handle INF.
	            System.out.println("Warning: invalid hValues detected by the Bellman-Ford Algorithm.");
	        }

	        final double reweight = edge.weight + values.get(edge.u) - values.get(edge.v);
	        reweightedAdjacencies.get(edge.u).addLast( new VertexAndWeight(edge.v, reweight) );	        
	    } );
	    
        // Step 4: Invoke Dijkstra's Algorithm starting from each vertex on the reweighted graph
	    List<List<Double>> allPairsShortestPaths = IntStream.range(0, vertexCount).boxed()
	    	.map( u -> dijkstraAlgorithm(vertexCount, reweightedAdjacencies, u, values) ).toList();
	    
        // Step 5: Return the result matrix
        return Optional.of(allPairsShortestPaths);
	}
	
	/**
	 * Return a list of shortest distances from the source vertex to all other vertices,
	 * or an empty optional if a negative cycle is detected
	 */
	private static Optional<List<Double>> bellmanFordAlgorithm(
			int augmentedVertexCount, List<Edge> edges, int sourceVertex) {
	   	List<Double> distances = Stream.generate( () -> INF ).limit(augmentedVertexCount).collect(Collectors.toList());
	   	distances.set(sourceVertex, 0.0);
	   	
	    // Relax the edges (augmentedVertexCount - 1) times
	   	boolean updated = true;
	    for ( int i = 0; i < augmentedVertexCount - 1 && updated; i++ ) {
	        updated = false;
	        for ( int j = 0; j < edges.size(); j++ ) {
	        	Edge edge = edges.get(j);
	            if ( distances.get(edge.u) != INF && distances.get(edge.u) + edge.weight < distances.get(edge.v) ) {
	                distances.set(edge.v, distances.get(edge.u) + edge.weight);
	                updated = true;
	            }
	        }
	    }

	    // Check for negative cycles in the graph
	    for ( Edge edge : edges ) {
	        if ( distances.get(edge.u) != INF && distances.get(edge.u) + edge.weight < distances.get(edge.v) ) {
	            return Optional.empty(); // Indicates to the calling method that a negative cycle has been detected
	        }
	    }

	    return Optional.of(distances);
	}
	
	/**
	 * Return a list of shortest path distances from the source vertex in the original graph to all other vertices
	 */
	private static List<Double> dijkstraAlgorithm(int vertexCount,
			Map<Integer, List<VertexAndWeight>> reweightedAdjacencies, int sourceVertex, List<Double> values) {
		List<Double> distances = IntStream.range(0, vertexCount).boxed().map( i -> INF ).collect(Collectors.toList());
	    distances.set(sourceVertex, 0.0);
	    
	    AbstractQueue<VertexAndWeight> priorityQueue = 
	    	new PriorityQueue<VertexAndWeight>( (a, b) -> Double.compare(a.weight, b.weight) ); 
	    priorityQueue.add( new VertexAndWeight(sourceVertex, 0.0) );

	    List<Double> finalDistances = IntStream.range(0, vertexCount).boxed()
	    		                               .map( i -> INF ).collect(Collectors.toList());
	    
	    while ( ! priorityQueue.isEmpty() ) {
	        VertexAndWeight vertexAndWeigth = priorityQueue.remove();
	        final int vertex = vertexAndWeigth.vertex;
	        if ( vertexAndWeigth.weight > distances.get(vertex) ) {
	            continue;
	        }
	        
	        // Store the final shortest path distance, translated back to the distance in the original graph
	        // which prevents processing vertices disconnected from the source vertex
	        if ( finalDistances.get(vertex) == INF ) {
	             if ( distances.get(vertex) == INF ) { // This should not happen, but is included as a safety check
	                 finalDistances.set(vertex, INF);
	             } else {
	                 // Translate distance back to its original weight: d(u,v) = d'(u,v) - h[u] + h[v]
	                 finalDistances.set(vertex, distances.get(vertex) - values.get(sourceVertex) + values.get(vertex));
	             }
	        }

	        // Relax the edges outgoing from vertex
	        if ( reweightedAdjacencies.containsKey(vertex) ) {
	            for ( VertexAndWeight pair : reweightedAdjacencies.get(vertex) ) {
	                if ( distances.get(vertex) != INF
	                	&& distances.get(vertex) + pair.weight < distances.get(pair.vertex) ) {
	                    distances.set(pair.vertex, distances.get(vertex) + pair.weight);
	                    priorityQueue.add( new VertexAndWeight(pair.vertex, distances.get(pair.vertex)) );
	                }
	            }
	        }	                    
	    }
	    
	    // Translate distance back to its original weight for any remaining reachable vertices
	    // This handles cases where a vertex was reachable, but was not the minimum vertex
	    // removed from the priority queue when its final distance was determined.
	    IntStream.range(0, vertexCount).forEach( i -> {
	         if ( finalDistances.get(i) == INF && distances.get(i) != INF ) {
	             finalDistances.set(i, distances.get(i) - values.get(sourceVertex) + values.get(i));
	         }
	    } );

	    return finalDistances;	    
	}
	  
	private record Edge(int u, int v, double weight) {}
	private record VertexAndWeight(int vertex, double weight) {}
	
	private static final double INF = Double.MAX_VALUE;

}
Output:
All pairs shortest paths:
The element (i, j) is the shortest path between vertex i and vertex j.
[0.0 -5.0 -1.0 0.0 ]
[INF 0.0 4.0 5.0 ]
[INF INF 0.0 1.0 ]
[INF INF INF 0.0 ]

JavaScript

Works with: NodeJS 16.14.2
Translation of: Java
// Johnson's Algorithm Implementation in JavaScript
// This algorithm finds all-pairs shortest paths in a weighted directed graph

// Constants
const INF = Number.MAX_VALUE;

// Edge class representing a directed edge from vertex u to vertex v with a weight
class Edge {
  constructor(u, v, weight) {
    this.u = u;
    this.v = v;
    this.weight = weight;
  }
}

// VertexAndWeight class for use in Dijkstra's algorithm
class VertexAndWeight {
  constructor(v, weight) {
    this.v = v;
    this.weight = weight;
  }
}

// WeightAndVertex class for use in the priority queue in Dijkstra's algorithm
class WeightAndVertex {
  constructor(weight, vertex) {
    this.weight = weight;
    this.vertex = vertex;
  }
}

// PriorityQueue implementation for Dijkstra's algorithm
class PriorityQueue {
  constructor() {
    this.items = [];
  }

  enqueue(weightAndVertex) {
    let added = false;
    for (let i = 0; i < this.items.length; i++) {
      if (weightAndVertex.weight < this.items[i].weight) {
        this.items.splice(i, 0, weightAndVertex);
        added = true;
        break;
      }
    }
    if (!added) {
      this.items.push(weightAndVertex);
    }
  }

  dequeue() {
    if (this.isEmpty()) return null;
    return this.items.shift();
  }

  isEmpty() {
    return this.items.length === 0;
  }
}

/**
 * Bellman-Ford algorithm to find shortest paths from a source vertex to all other vertices
 * Returns null if there is a negative cycle
 */
function bellmanFordAlgorithm(augmentedVertexCount, edges, sourceVertex) {
  // Initialize distances array with INF, except for the source vertex
  const distances = Array(augmentedVertexCount).fill(INF);
  distances[sourceVertex] = 0.0;

  // Relax the edges (augmentedVertexCount - 1) times
  let updated = true;
  for (let i = 0; i < augmentedVertexCount - 1 && updated; i++) {
    updated = false;
    for (let j = 0; j < edges.length; j++) {
      const edge = edges[j];
      if (distances[edge.u] !== INF && distances[edge.u] + edge.weight < distances[edge.v]) {
        distances[edge.v] = distances[edge.u] + edge.weight;
        updated = true;
      }
    }
  }

  // Check for negative cycles in the graph
  for (const edge of edges) {
    if (distances[edge.u] !== INF && distances[edge.u] + edge.weight < distances[edge.v]) {
      return null; // Indicates a negative cycle has been detected
    }
  }

  return distances;
}

/**
 * Dijkstra's algorithm to find shortest paths from a source vertex to all other vertices
 * in a graph with non-negative edge weights
 */
function dijkstraAlgorithm(vertexCount, reweightedAdjacencies, sourceVertex, values) {
  // Initialize distances array with INF
  const distances = Array(vertexCount).fill(INF);
  distances[sourceVertex] = 0.0;

  const priorityQueue = new PriorityQueue();
  priorityQueue.enqueue(new WeightAndVertex(0.0, sourceVertex));

  const finalDistances = Array(vertexCount).fill(INF);

  while (!priorityQueue.isEmpty()) {
    const weightAndVertex = priorityQueue.dequeue();
    const vertex = weightAndVertex.vertex;
    
    if (weightAndVertex.weight > distances[vertex]) {
      continue;
    }

    // Store the final shortest path distance, translated back to the distance in the original graph
    if (finalDistances[vertex] === INF) {
      if (distances[vertex] === INF) { // This should not happen, but is included as a safety check
        finalDistances[vertex] = INF;
      } else {
        // Translate distance back to its original weight: d(u,v) = d'(u,v) - h[u] + h[v]
        finalDistances[vertex] = distances[vertex] - values[sourceVertex] + values[vertex];
      }
    }

    // Relax the edges outgoing from vertex
    if (reweightedAdjacencies.has(vertex)) {
      for (const pair of reweightedAdjacencies.get(vertex)) {
        if (distances[vertex] !== INF && distances[vertex] + pair.weight < distances[pair.v]) {
          distances[pair.v] = distances[vertex] + pair.weight;
          priorityQueue.enqueue(new WeightAndVertex(distances[pair.v], pair.v));
        }
      }
    }
  }

  // Translate distance back to its original weight for any remaining reachable vertices
  for (let i = 0; i < vertexCount; i++) {
    if (finalDistances[i] === INF && distances[i] !== INF) {
      finalDistances[i] = distances[i] - values[sourceVertex] + values[i];
    }
  }

  return finalDistances;
}

/**
 * Johnson's algorithm to find shortest paths between all pairs of vertices
 * in a weighted directed graph which may contain negative edge weights
 */
function johnsonsAlgorithm(graph) {
  const vertexCount = graph.length;
  const originalEdges = [];

  // Step 0: Build a list of edges for the original graph
  for (let i = 0; i < vertexCount; i++) {
    for (let j = 0; j < vertexCount; j++) {
      const weight = graph[i][j];
      if (i === j) {
        if (weight !== 0.0) {
          console.log(`Warning: graph[i][i] for i = ${i} is ${weight}, expected to be 0.0, resetting it to 0.0`);
        }
      } else if (weight !== INF) {
        originalEdges.push(new Edge(i, j, weight));
      }
    }
  }

  // Step 1: Form the augmented graph
  // Add a new vertex with index 'vertexCount' and having 0-weight edges to all the original vertices
  const augmentedEdges = [...originalEdges];
  for (let i = 0; i < vertexCount; i++) {
    augmentedEdges.push(new Edge(vertexCount, i, 0.0));
  }

  // Step 2: Invoke the Bellman-Ford Algorithm starting from the new vertex
  const hValues = bellmanFordAlgorithm(vertexCount + 1, augmentedEdges, vertexCount);

  if (hValues === null) {
    return null; // A negative cycle was detected by the Bellman-Ford Algorithm
  }

  // Remove the value for the augmented vertex
  hValues.pop();

  // Step 3: Reweight the edges
  const reweightedAdjacencies = new Map();
  for (let i = 0; i < vertexCount; i++) {
    reweightedAdjacencies.set(i, []);
  }

  for (const edge of originalEdges) {
    // Ensure the 'hValues' are valid before reweighting
    if (hValues[edge.u] === INF || hValues[edge.v] === INF) {
      // This can happen if the original graph was not strongly connected to the augmented vertex.
      // While not strictly an error for Johnson's Algorithm, because paths might still exist between
      // reachable nodes, it means the reweighting might involve INF.
      // Computation can proceed since Dijkstra's Algorithm can handle INF.
      console.log("Warning: invalid hValues detected by the Bellman-Ford Algorithm.");
    }

    const reweight = edge.weight + hValues[edge.u] - hValues[edge.v];
    reweightedAdjacencies.get(edge.u).push(new VertexAndWeight(edge.v, reweight));
  }

  // Step 4: Invoke Dijkstra's Algorithm starting from each vertex on the reweighted graph
  const allPairsShortestPaths = [];
  for (let u = 0; u < vertexCount; u++) {
    allPairsShortestPaths.push(dijkstraAlgorithm(vertexCount, reweightedAdjacencies, u, hValues));
  }

  // Step 5: Return the result matrix
  return allPairsShortestPaths;
}

// Example usage
function main() {
  // The element (i, j) is the weight of the edge from vertex i to vertex j.
  // INF, for infinity, means that there is no edge from vertex i to vertex j.
  const graph = [
    [0.0, -5.0, 2.0, 3.0],
    [INF, 0.0, 4.0, INF],
    [INF, INF, 0.0, 1.0],
    [INF, INF, INF, 0.0]
  ];

  const result = johnsonsAlgorithm(graph);

  if (result) {
    console.log("All pairs shortest paths:");
    console.log("The element (i, j) is the shortest path between vertex i and vertex j.");
    for (const row of result) {
      let rowStr = "[";
      for (const number of row) {
        rowStr += (number === INF) ? "INF " : number + " ";
      }
      rowStr += "]";
      console.log(rowStr);
    }
  } else {
    console.log("A negative cycle was detected in the graph.");
  }
}

// Run the example
main();
Output:
All pairs shortest paths:
The element (i, j) is the shortest path between vertex i and vertex j.
[0 -5 -1 0 ]
[INF 0 4 5 ]
[INF INF 0 1 ]
[INF INF INF 0 ]

Julia

using Graphs

const edges = [(1, 2), (1, 3), (1, 4), (2, 3), (3, 4)]
const weights = [Inf -5.0 2.0 3.0; Inf 0.0 4.0 Inf; Inf Inf 0.0 1.0; Inf Inf Inf 0.0]

g = SimpleGraph(4) # 4 vertices
for (v1, v2) in edges # edge vertices are from a one-based vertex array
    add_edge!(g, v1, v2)
end

johnson_state = johnson_shortest_paths(g, weights)
display(johnson_state.dists)
Output:
4×4 Matrix{Float64}:
  0.0  -5.0  -1.0  0.0
 Inf    0.0   4.0  5.0
 Inf   Inf    0.0  1.0
 Inf   Inf   Inf   0.0

Phix

Translation of: Go
constant inf = 1e308*1e308

// bellmanFordAlgorithm returns a list of shortest distances from the source vertex to all other vertices,
// and a boolean indicating whether a negative cycle was detected
function bellmanFordAlgorithm(integer augmentedVertexCount, sequence edges, integer sourceVertex)
    sequence distances := repeat(inf, augmentedVertexCount)
    distances[sourceVertex] = 0.0

    // Relax the edges (augmentedVertexCount - 1) times
    integer u, v
    atom weight
    for i=1 to augmentedVertexCount-1 do
        bool updated = false
        for edge in edges do
            {u,v,weight} = edge
            if distances[u] != inf and distances[u]+weight < distances[v] then
                distances[v] = distances[u] + weight
                updated = true
            end if
        end for
        if not updated then exit end if
    end for

    // Check for negative cycles in the graph
    for edge in edges do
        {u,v,weight} = edge
        if distances[u] != inf and distances[u]+weight < distances[v] then
            return {{}, true} // Indicates to the calling method that a negative cycle has been detected
        end if
    end for

    return {distances, false}
end function

// dijkstraAlgorithm returns a list of shortest path distances from the source vertex in the original graph to all other vertices
function dijkstraAlgorithm(integer vertexCount, sequence reweightedAdjacencies, integer sourceVertex, sequence vals)
    sequence distances := repeat(inf, vertexCount),
        finalDistances := repeat(inf, vertexCount)
    distances[sourceVertex] = 0.0

    integer pq := pq_new()
    pq_add({sourceVertex,0.0},pq)
    atom weight
    integer vertex, v

    while pq_size(pq) do
        {vertex,weight} = pq_pop(pq)
        if weight <= distances[vertex] then

            // Store the final shortest path distance, translated back to the distance in the original graph
            // which prevents processing vertices disconnected from the source vertex
            if finalDistances[vertex] == inf then
                if distances[vertex] == inf then // This should not happen, but is included as a safety check
                    finalDistances[vertex] = inf
                else
                    // Translate distance back to its original weight: d(u,v) = d'(u,v) - h[u] + h[v]
                    finalDistances[vertex] = distances[vertex] - vals[sourceVertex] + vals[vertex]
                end if
            end if

            // Relax the edges outgoing from vertex
            sequence adjList := reweightedAdjacencies[vertex]
            for pair in adjList do
                {v,weight} = pair
                if distances[vertex] != inf and distances[vertex]+weight < distances[v] then
                    distances[v] = distances[vertex] + weight
                    pq_add({v,distances[v]},pq)
                end if
            end for
        end if
    end while
    pq_destroy(pq)

    // Translate distance back to its original weight for any remaining reachable vertices
    // This handles cases where a vertex was reachable, but was not the minimum vertex
    // removed from the priority queue when its final distance was determined.
    for i=1 to vertexCount do
        if finalDistances[i] == inf and distances[i] != inf then
            finalDistances[i] = distances[i] - vals[sourceVertex] + vals[i]
        end if
    end for

    return finalDistances
end function

// johnsonsAlgorithm returns the shortest path between all pairs of vertices in an edge weighted directed graph
// and a boolean indicating whether a negative cycle was detected
function johnsonsAlgorithm(sequence graph)
    integer vertexCount := length(graph)
    sequence originalEdges := {}

    // Step 0: Build a list of edges for the original graph
    for i=1 to vertexCount do
        for j=1 to vertexCount do
            atom weight := graph[i][j]
            if i == j then
                assert(weight==0.0)
            elsif weight != inf then
                originalEdges = append(originalEdges, {i, j, weight})
            end if
        end for
    end for

    // Step 1: Form the augmented graph
    // Add a new vertex with index 'vertexCount' and having 0-weight edges to all the original vertices
    sequence augmentedEdges := deep_copy(originalEdges)
    for i=1 to vertexCount do
        augmentedEdges = append(augmentedEdges, {vertexCount+1, i, 0.0})
    end for

    // Step 2: Invoke the Bellman-Ford Algorithm starting from the new vertex
    {sequence hValues, bool hasNegativeCycle} := bellmanFordAlgorithm(vertexCount+1, augmentedEdges, vertexCount+1)

    if hasNegativeCycle then
        return {{}, true} // A negative cycle was detected by the Bellman-Ford Algorithm
    end if

    sequence vals := hValues[1..vertexCount] // Remove the value for the augmented vertex

    // Step 3: Reweight the edges
    sequence reweightedAdjacencies := repeat({},vertexCount)
    integer u, v
    atom weight
    for edge in originalEdges do
        {u,v,weight} = edge
        // Ensure the 'vals' are valid before reweighting
        if vals[u] == inf or vals[v] == inf then
            // This can happen if the original graph was not strongly connected to the augmented vertex.
            // While not strictly an error for Johnson's Algorithm, because paths might still exist between
            // reachable nodes, it means the reweighting might involve INF.
            // Computation can proceed since Dijkstra's Algorithm can handle INF.
            printf(1,"Warning: invalid hValues detected by the Bellman-Ford Algorithm.\n")
        end if

        atom reweight := weight + vals[u] - vals[v]
        reweightedAdjacencies[u] = append(reweightedAdjacencies[u], {v, reweight})
    end for

    // Step 4: Invoke Dijkstra's Algorithm starting from each vertex on the reweighted graph
    sequence allPairsShortestPaths := repeat(0, vertexCount)
    for u=1 to vertexCount do
        allPairsShortestPaths[u] = dijkstraAlgorithm(vertexCount, reweightedAdjacencies, u, vals)
    end for

    // Step 5: Return the result matrix
    return {allPairsShortestPaths, false}
end function

// The element (i, j) is the weight of the edge from vertex i to vertex j.
// inf, for infinity, means that there is no edge from vertex i to vertex j.
sequence graph := {{0.0,-5.0, 2.0, 3.0},
                   {inf, 0.0, 4.0, inf},
                   {inf, inf, 0.0, 1.0},
                   {inf, inf, inf, 0.0}}

{sequence result, bool hasNegativeCycle} := johnsonsAlgorithm(graph)

if hasNegativeCycle then
    printf(1,"A negative cycle was detected in the graph.\n")
else
    printf(1,"All pairs shortest paths:\n")
    printf(1,"The element (i, j) is the shortest path between vertex i and vertex j.\n")
    pp(result,{pp_Nest,1})
end if
Output:
All pairs shortest paths:
The element (i, j) is the shortest path between vertex i and vertex j.
{{0,-5,-1,0},
 {inf,0,4,5},
 {inf,inf,0,1},
 {inf,inf,inf,0}}

Python

import heapq
import itertools # Used for counter in Bellman-Ford

INF = float('inf')

def bellman_ford(num_vertices, edges, source):
    """
    Runs Bellman-Ford algorithm from a source vertex.

    Args:
        num_vertices: The total number of vertices (including the augmented source).
        edges: A list of tuples (u, v, weight) representing directed edges.
        source: The index of the source vertex.

    Returns:
        A list of shortest distances 'h' from the source to all other vertices,
        or None if a negative cycle is detected.
    """
    dist = [INF] * num_vertices
    dist[source] = 0

    # Relax edges V-1 times
    for _ in range(num_vertices - 1):
        updated = False
        for u, v, weight in edges:
            if dist[u] != INF and dist[u] + weight < dist[v]:
                dist[v] = dist[u] + weight
                updated = True
        # If no update in a full pass, we can stop early
        if not updated:
            break

    # Check for negative cycles
    for u, v, weight in edges:
        if dist[u] != INF and dist[u] + weight < dist[v]:
            print("Graph contains a negative weight cycle")
            return None # Indicate negative cycle

    return dist

def dijkstra(num_vertices, adj, source, h_values):
    """
    Runs Dijkstra's algorithm on a potentially re-weighted graph.

    Args:
        num_vertices: The number of vertices in the original graph.
        adj: Adjacency list of the re-weighted graph {u: [(v, reweighted_weight), ...]}.
        source: The source vertex index for this run.
        h_values: The potential values calculated by Bellman-Ford.

    Returns:
        A list of shortest path distances from the source in the original graph.
    """
    dist = [INF] * num_vertices
    dist[source] = 0
    
    # Priority queue stores (distance, vertex)
    pq = [(0, source)] 

    final_dist = [INF] * num_vertices # To store results

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

        # If we found a shorter path already, skip
        if d > dist[u]:
            continue
            
        # Store the final shortest path distance (translated back)
        # This check prevents processing nodes disconnected from source
        if final_dist[u] == INF: 
             if dist[u] == INF: # Should not happen if popped from pq, but safety check
                 final_dist[u] = INF
             else:
                 # Translate distance back to original weight: d(u,v) = d'(u,v) - h[u] + h[v]
                 # Here, d'(source, u) is dist[u]
                 # So, original distance = dist[u] - h[source] + h[u]
                 final_dist[u] = dist[u] - h_values[source] + h_values[u]


        # Relax edges outgoing from u
        if u in adj:
            for v, reweighted_weight in adj[u]:
                if dist[u] != INF and dist[u] + reweighted_weight < dist[v]:
                    dist[v] = dist[u] + reweighted_weight
                    heapq.heappush(pq, (dist[v], v))

    # After Dijkstra finishes, translate any remaining reachable vertices
    # This handles cases where a node might be reachable but wasn't the
    # minimum popped from PQ when its final distance was determined.
    for i in range(num_vertices):
         if final_dist[i] == INF and dist[i] != INF:
             final_dist[i] = dist[i] - h_values[source] + h_values[i]


    return final_dist


def johnsons_algorithm(graph_matrix):
    """
    Implements Johnson's algorithm for all-pairs shortest paths.

    Args:
        graph_matrix: An adjacency matrix representation of the graph.
                      graph_matrix[i][j] is the weight of the edge from i to j.
                      Use 0 for non-existent edges between different nodes (or INF).
                      graph_matrix[i][i] should be 0.

    Returns:
        A matrix containing the shortest path distances between all pairs,
        or None if a negative cycle is detected. Returns INF if no path exists.
    """
    V = len(graph_matrix)
    original_edges = []
    
    # --- Step 0: Handle Input and Build Edge List for Original Graph ---
    # Be careful about 0 vs non-existent edge. Assume 0 means no edge if i != j
    for i in range(V):
        for j in range(V):
            weight = graph_matrix[i][j]
            # Only add edges that exist. Assuming 0 means no edge unless i==j.
            # If 0 could be a valid edge weight, use INF for non-edges in input.
            if i == j:
                if weight != 0:
                   print(f"Warning: graph_matrix[{i}][{i}] is {weight}, expected 0. Setting to 0.")
                   # Optional: Raise error instead? Or just proceed assuming 0?
                   # graph_matrix[i][i] = 0 # Correct it if needed by Bellman-Ford/Dijkstra logic
            elif weight != 0: # Assuming 0 means non-edge here
                 original_edges.append((i, j, weight))
            # If 0 IS a valid weight, the condition should be:
            # elif weight != INF: # Use INF in the input matrix for non-edges
            #    original_edges.append((i, j, weight))


    # --- Step 1: Form the augmented graph G' ---
    # Add a new vertex 's' (index V) with 0-weight edges to all original vertices
    augmented_edges = list(original_edges)
    for i in range(V):
        augmented_edges.append((V, i, 0)) # New source V connects to all others

    num_vertices_augmented = V + 1

    # --- Step 2: Run Bellman-Ford from the new source 's' ---
    h_values = bellman_ford(num_vertices_augmented, augmented_edges, V)

    if h_values is None:
        # Negative cycle detected by Bellman-Ford
        return None

    # Remove the h value for the augmented source, we only need it for original vertices
    h_values = h_values[:V] # Keep h[0] to h[V-1]

    # --- Step 3: Reweight the edges ---
    reweighted_adj = {i: [] for i in range(V)}
    for u, v, weight in original_edges:
        # Ensure h values are valid before reweighting
        if h_values[u] == INF or h_values[v] == INF:
            # This can happen if the original graph wasn't strongly connected
            # from the augmented source 's'. While not strictly an error for
            # Johnson's (paths might still exist between reachable nodes),
            # it means the reweighting might involve INF.
            # Let's compute the reweighted value anyway; Dijkstra handles INF.
            pass # Or print a warning if desired

        reweighted_weight = weight + h_values[u] - h_values[v]
        # Theoretically, reweighted_weight should be >= 0 if no negative cycle
        # Add small tolerance for floating point issues if necessary:
        # if reweighted_weight < -1e-9:
        #     print(f"Warning: Potential negative reweighted edge {u}->{v}: {reweighted_weight}")
        reweighted_adj[u].append((v, reweighted_weight))

    # --- Step 4: Run Dijkstra from each vertex on the reweighted graph ---
    all_pairs_shortest_paths = [[INF for _ in range(V)] for _ in range(V)]

    for u in range(V):
        # Run Dijkstra on the reweighted graph starting from u
        shortest_paths_from_u = dijkstra(V, reweighted_adj, u, h_values)
        all_pairs_shortest_paths[u] = shortest_paths_from_u
        # The dijkstra implementation now directly calculates the original distance

    # --- Step 5: Return the result matrix ---
    return all_pairs_shortest_paths

# --- Test Case ---
graph = [[0, -5,  2,  3],
         [0,  0,  4,  0], # Assuming 0 means no edge from 1->0 and 1->3
         [0,  0,  0,  1], # Assuming 0 means no edge from 2->0 and 2->1
         [0,  0,  0,  0]] # Assuming 0 means no edge from 3->0, 3->1, 3->2

result = johnsons_algorithm(graph)

if result:
    print("All-pairs shortest paths:")
    for row in result:
        print([x if x != INF else "INF" for x in row])
else:
    print("Negative cycle detected in the graph.")

print("\nExpected for the test case:")
print("[0, -5, -1, 0]")
print("['INF', 0, 4, 5]")
print("['INF', 'INF', 0, 1]")
print("['INF', 'INF', 'INF', 0]")
Output:
All-pairs shortest paths:
[0, -5, -1, 0]
['INF', 0, 4, 5]
['INF', 'INF', 0, 1]
['INF', 'INF', 'INF', 0]

Expected for the test case:
[0, -5, -1, 0]
['INF', 0, 4, 5]
['INF', 'INF', 0, 1]
['INF', 'INF', 'INF', 0]

Rust

Translation of: Java
use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashMap};
use std::f64::MAX as INF;

#[derive(Debug, Clone, Copy)]
struct Edge {
    u: usize,
    v: usize,
    weight: f64,
}

#[derive(Debug, Clone, Copy)]
struct VertexAndWeight {
    v: usize,
    weight: f64,
}

// Custom wrapper for f64 to implement Ord
#[derive(Debug, Clone, Copy)]
struct OrderedFloat(f64);

impl PartialEq for OrderedFloat {
    fn eq(&self, other: &Self) -> bool {
        self.0.eq(&other.0)
    }
}

// This is technically not sound for NaN values, but for our algorithm we won't have NaN
impl Eq for OrderedFloat {}

impl PartialOrd for OrderedFloat {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.0.partial_cmp(&other.0)
    }
}

impl Ord for OrderedFloat {
    fn cmp(&self, other: &Self) -> Ordering {
        self.partial_cmp(other).unwrap_or(Ordering::Equal)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct WeightAndVertex {
    weight: OrderedFloat,
    vertex: usize,
}

impl PartialOrd for WeightAndVertex {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for WeightAndVertex {
    fn cmp(&self, other: &Self) -> Ordering {
        // Min heap (reverse ordering)
        other.weight.cmp(&self.weight)
    }
}

fn johnsons_algorithm(graph: &Vec<Vec<f64>>) -> Option<Vec<Vec<f64>>> {
    let vertex_count = graph.len();
    let mut original_edges = Vec::new();

    // Step 0: Build a list of edges for the original graph
    for i in 0..vertex_count {
        for j in 0..vertex_count {
            let weight = graph[i][j];
            if i == j {
                if weight != 0.0 {
                    println!("Warning: graph[i][i] for i = {} is {}, expected to be 0.0, resetting it to 0.0", i, weight);
                }
            } else if weight != INF {
                original_edges.push(Edge { u: i, v: j, weight });
            }
        }
    }

    // Step 1: Form the augmented graph
    // Add a new vertex with index 'vertex_count' and having 0-weight edges to all the original vertices
    let mut augmented_edges = original_edges.clone();
    for i in 0..vertex_count {
        augmented_edges.push(Edge { u: vertex_count, v: i, weight: 0.0 });
    }

    // Step 2: Invoke the Bellman-Ford Algorithm starting from the new vertex
    let h_values_opt = bellman_ford_algorithm(vertex_count + 1, &augmented_edges, vertex_count);

    if h_values_opt.is_none() {
        return None; // A negative cycle was detected by the Bellman-Ford Algorithm
    }

    let mut h_values = h_values_opt.unwrap();
    h_values.pop(); // Remove the value for the augmented vertex

    // Step 3: Reweight the edges
    let mut reweighted_adjacencies: HashMap<usize, Vec<VertexAndWeight>> = 
        (0..vertex_count).map(|v| (v, Vec::new())).collect();

    for edge in &original_edges {
        // Ensure the 'values' are valid before reweighting
        if h_values[edge.u] == INF || h_values[edge.v] == INF {
            // This can happen if the original graph was not strongly connected to the augmented vertex.
            // While not strictly an error for Johnson's Algorithm, because paths might still exist between
            // reachable nodes, it means the reweighting might involve INF.
            // Computation can proceed since Dijkstra's Algorithm can handle INF.
            println!("Warning: invalid hValues detected by the Bellman-Ford Algorithm.");
        }

        let reweight = edge.weight + h_values[edge.u] - h_values[edge.v];
        reweighted_adjacencies.get_mut(&edge.u).unwrap().push(VertexAndWeight { v: edge.v, weight: reweight });
    }

    // Step 4: Invoke Dijkstra's Algorithm starting from each vertex on the reweighted graph
    let all_pairs_shortest_paths: Vec<Vec<f64>> = (0..vertex_count)
        .map(|u| dijkstra_algorithm(vertex_count, &reweighted_adjacencies, u, &h_values))
        .collect();

    // Step 5: Return the result matrix
    Some(all_pairs_shortest_paths)
}

fn bellman_ford_algorithm(
    augmented_vertex_count: usize,
    edges: &Vec<Edge>,
    source_vertex: usize,
) -> Option<Vec<f64>> {
    let mut distances = vec![INF; augmented_vertex_count];
    distances[source_vertex] = 0.0;

    // Relax the edges (augmented_vertex_count - 1) times
    let mut updated = true;
    for _ in 0..(augmented_vertex_count - 1) {
        if !updated {
            break;
        }
        updated = false;
        for edge in edges {
            if distances[edge.u] != INF && distances[edge.u] + edge.weight < distances[edge.v] {
                distances[edge.v] = distances[edge.u] + edge.weight;
                updated = true;
            }
        }
    }

    // Check for negative cycles in the graph
    for edge in edges {
        if distances[edge.u] != INF && distances[edge.u] + edge.weight < distances[edge.v] {
            return None; // Indicates to the calling method that a negative cycle has been detected
        }
    }

    Some(distances)
}

fn dijkstra_algorithm(
    vertex_count: usize,
    reweighted_adjacencies: &HashMap<usize, Vec<VertexAndWeight>>,
    source_vertex: usize,
    values: &Vec<f64>,
) -> Vec<f64> {
    let mut distances = vec![INF; vertex_count];
    distances[source_vertex] = 0.0;

    let mut priority_queue = BinaryHeap::new();
    priority_queue.push(WeightAndVertex {
        weight: OrderedFloat(0.0),
        vertex: source_vertex,
    });

    let mut final_distances = vec![INF; vertex_count];

    while let Some(weight_and_vertex) = priority_queue.pop() {
        let vertex = weight_and_vertex.vertex;
        if weight_and_vertex.weight.0 > distances[vertex] {
            continue;
        }

        // Store the final shortest path distance, translated back to the distance in the original graph
        // which prevents processing vertices disconnected from the source vertex
        if final_distances[vertex] == INF {
            if distances[vertex] == INF {
                // This should not happen, but is included as a safety check
                final_distances[vertex] = INF;
            } else {
                // Translate distance back to its original weight: d(u,v) = d'(u,v) - h[u] + h[v]
                final_distances[vertex] = distances[vertex] - values[source_vertex] + values[vertex];
            }
        }

        // Relax the edges outgoing from vertex
        if let Some(adjacencies) = reweighted_adjacencies.get(&vertex) {
            for pair in adjacencies {
                if distances[vertex] != INF && distances[vertex] + pair.weight < distances[pair.v] {
                    distances[pair.v] = distances[vertex] + pair.weight;
                    priority_queue.push(WeightAndVertex {
                        weight: OrderedFloat(distances[pair.v]),
                        vertex: pair.v,
                    });
                }
            }
        }
    }

    // Translate distance back to its original weight for any remaining reachable vertices
    // This handles cases where a vertex was reachable, but was not the minimum vertex
    // removed from the priority queue when its final distance was determined.
    for i in 0..vertex_count {
        if final_distances[i] == INF && distances[i] != INF {
            final_distances[i] = distances[i] - values[source_vertex] + values[i];
        }
    }

    final_distances
}

fn main() {
    // The element (i, j) is the weight of the edge from vertex i to vertex j.
    // INF, for infinity, means that there is no edge from vertex i to vertex j.
    let graph = vec![
        vec![0.0, -5.0, 2.0, 3.0],
        vec![INF, 0.0, 4.0, INF],
        vec![INF, INF, 0.0, 1.0],
        vec![INF, INF, INF, 0.0],
    ];

    match johnsons_algorithm(&graph) {
        Some(result) => {
            println!("All pairs shortest paths:");
            println!("The element (i, j) is the shortest path between vertex i and vertex j.");
            for row in result {
                print!("[");
                for number in row {
                    if number == INF {
                        print!("INF ");
                    } else {
                        print!("{} ", number);
                    }
                }
                println!("]");
            }
        }
        None => {
            println!("A negative cycle was detected in the graph.");
        }
    }
}
Output:
All pairs shortest paths:
The element (i, j) is the shortest path between vertex i and vertex j.
[0 -5 -1 0 ]
[INF 0 4 5 ]
[INF INF 0 1 ]
[INF INF INF 0 ]


Wren

Translation of: Python
var INF = Num.infinity

/*  Runs Bellman-Ford algorithm from a source vertex.

    Args:
        numVertices: The total number of vertices (including the augmented source).
        edges: A list of tuples (u, v, weight) representing directed edges.
        source: The index of the source vertex.

    Returns:
        A list of shortest distances 'h' from the source to all other vertices,
        or null if a negative cycle is detected.
*/
var bellmanFord = Fn.new { |numVertices, edges, source|
    var dist = List.filled(numVertices, INF)
    dist[source] = 0

    // Relax edges V - 1 times.
    for (i in 0...numVertices - 1) {
        var updated = false
        for (e in edges) {
            var u = e[0]
            var v = e[1]
            var w = e[2]
            if (dist[u] != INF && dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w
                updated = true
            }
        }
        // If no update in a full pass, we can stop early.
        if (!updated) break
    }

    // Check for negative cycles.
    for (e in edges) {
        var u = e[0]
        var v = e[1]
        var w = e[2]
        if (dist[u] != INF && dist[u] + w < dist[v]) {
            System.print("Graph contains a negative weight cycle")
            return null // indicate negative cycle
        }
    }
    return dist
}

/*  Runs Dijkstra's algorithm on a potentially re-weighted graph.

    Args:
        numVertices: The number of vertices in the original graph.
        adj: Adjacency list of the re-weighted graph.
        source: The source vertex index for this run.
        hValues: The potential values calculated by Bellman-Ford.

    Returns:
        A list of shortest path distances from the source in the original graph.
*/
var dijkstra = Fn.new { |numVertices, adj, source, hValues|
    var dist = List.filled(numVertices, INF)
    dist[source] = 0

    // Priority queue stores (distance, vertex).
    var pq = [[0, source]]

    var finalDist = List.filled(numVertices, INF)  // to store results
    while (!pq.isEmpty) {
        var du = pq.removeAt(0)
        var d = du[0]
        var u = du[1]
        // If we found a shorter path already, skip.
        if (d > dist[u]) continue

        // Store the final shortest path distance (translated back).
        // This check prevents processing nodes disconnected from source.
        if (finalDist[u] == INF) {
            if (dist[u] == INF) {  // Should not happen if popped from pq, but safety check
                finalDist[u] = INF
            } else {
                // Translate distance back to original weight: d(u,v) = d'(u,v) - h[u] + h[v]
                // Here, d'(source, u) is dist[u]
                // So, original distance = dist[u] - h[source] + h[u]
                finalDist[u] = dist[u] - hValues[source] + hValues[u]
            }
        }

        // Relax edges outgoing from u.
        if (adj.containsKey(u)) {
            for (vr in adj[u]) {
                var v = vr[0]
                var rw = vr[1]
                if (dist[u] != INF && dist[u] + rw < dist[v]) {
                    dist[v] = dist[u] + rw
                    pq.add([dist[v], v])
                    pq.sort { |a, b| a[0] < b[0] }
                }
            }
        }
    }

    // After Dijkstra finishes, translate any remaining reachable vertices.
    // This handles cases where a node might be reachable but wasn't the
    // minimum popped from PQ when its final distance was determined.
    for (i in 0...numVertices) {
        if (finalDist[i] == INF && dist[i] != INF) {
            finalDist[i] = dist[i] - hValues[source] + hValues[i]
        }
    }

    return finalDist
}

/* Implements Johnson's algorithm for all-pairs shortest paths.

    Args:
        graph_matrix: An adjacency matrix representation of the graph.
                      graphMatrix[i][j] is the weight of the edge from i to j.
                      Use 0 for non-existent edges between different nodes (or INF).
                      graphMatrix[i][i] should be 0.

    Returns:
        A matrix containing the shortest path distances between all pairs,
        or null if a negative cycle is detected. Returns INF if no path exists.
*/
var johnsonsAlgorithm = Fn.new { |graphMatrix|
    var V = graphMatrix.count
    var originalEdges = []

    // --- Step 0: Handle Input and Build Edge List for Original Graph ---
    // Be careful about 0 vs non-existent edge. Assume 0 means no edge if i != j.
    for (i in 0...V) {
        for (j in 0...V) {
            var weight = graphMatrix[i][j]
            // Only add edges that exist. Assuming 0 means no edge unless i==j.
            // If 0 could be a valid edge weight, use INF for non-edges in input.
            if (i == j) {
                if (weight != 0) {
                    System.print("Warning: graphMatrix[%(i)][%(i)] is %(weight), expected 0. Setting to 0.")
                }
            } else if (weight != 0) {  // assuming 0 means non-edge here
                originalEdges.add([i, j, weight])
                // If 0 IS a valid weight, the condition should be:
                // else if (weight != INF) { // Use INF in the input matrix for non-edges
                //     originalEdges.add([i, j, weight])
            }
        }
    }

    // --- Step 1: Form the augmented graph G' ---
    // Add a new vertex 's' (index V) with 0-weight edges to all original vertices.
    var augmentedEdges = List.filled(originalEdges.count, null)
    for (i in 0...originalEdges.count) augmentedEdges[i] = originalEdges[i].toList
    for (i in 0...V) {
        augmentedEdges.add([V, i, 0])  // new source V connects to all others
    }

    var numVerticesAugmented = V + 1

    // --- Step 2: Run Bellman-Ford from the new source 's' ---
    var hValues = bellmanFord.call(numVerticesAugmented, augmentedEdges, V)

    if (!hValues) {
        // Negative cycle detected by Bellman-Ford
        return null
    }

    // Remove the h value for the augmented source, we only need it for original vertices.
    hValues = hValues[0...V]  // Keep h[0] to h[V-1]

    // --- Step 3: Reweight the edges ---
    var reweightedAdj = {}
    for (i in 0...V) reweightedAdj[i] = []
    for (e in originalEdges) {
        var u = e[0]
        var v = e[1]
        var w = e[2]
        // Ensure h values are valid before reweighting.
        if (hValues[u] == INF || hValues[v] == INF) {
            // This can happen if the original graph wasn't strongly connected
            // from the augmented source 's'. While not strictly an error for
            // Johnson's (paths might still exist between reachable nodes),
            // it means the reweighting might involve INF.
            // Let's compute the reweighted value anyway; Dijkstra handles INF.
            System.print("Warning: invalid h values detected.")
        }

        var rw = w + hValues[u] - hValues[v]
        // Theoretically, rw should be >= 0 if no negative cycle.
        // Add small tolerance for floating point issues if necessary:
        // if (rw < -1e-9) {
        //     System.print("Warning: Potential negative reweighted edge %(u)->%(v): %(rw)")
        // }
        reweightedAdj[u].add([v, rw])
    }

    // --- Step 4: Run Dijkstra from each vertex on the reweighted graph ---
    var allPairsShortestPaths = (0...V).map { |i| List.filled(V, INF) }.toList

    for (u in 0...V) {
        // Run Dijkstra on the reweighted graph starting from u
        var shortestPathsFromU = dijkstra.call(V, reweightedAdj, u, hValues)
        allPairsShortestPaths[u] = shortestPathsFromU
        // The dijkstra implementation now directly calculates the original distance.
    }

    // --- Step 5: Return the result matrix ---
    return allPairsShortestPaths
}

// --- Test Case ---
var graph = [[0, -5,  2,  3],
             [0,  0,  4,  0],  // assuming 0 means no edge from 1->0 and 1->3
             [0,  0,  0,  1],  // assuming 0 means no edge from 2->0 and 2->1
             [0,  0,  0,  0]]  // assuming 0 means no edge from 3->0, 3->1, 3->2

var result = johnsonsAlgorithm.call(graph)

if (result) {
    System.print("All-pairs shortest paths:")
    for (row in result) {
        System.print(row.toString.replace("infinity", "'INF'"))
    }
} else {
    System.print("Negative cycle detected in the graph.")
}

System.print("\nExpected for the test case:")
System.print("[0, -5, -1, 0]")
System.print("['INF', 0, 4, 5]")
System.print("['INF', 'INF', 0, 1]")
System.print("['INF', 'INF', 'INF', 0]")
Output:
All-pairs shortest paths:
[0, -5, -1, 0]
['INF', 0, 4, 5]
['INF', 'INF', 0, 1]
['INF', 'INF', 'INF', 0]

Expected for the test case:
[0, -5, -1, 0]
['INF', 0, 4, 5]
['INF', 'INF', 0, 1]
['INF', 'INF', 'INF', 0]


Zig

Warning:The wrong Zig code needs to be corrected.
Works with: Zig 0.14.0
Translation of: Java
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const AutoHashMap = std.AutoHashMap;
const PriorityQueue = std.PriorityQueue;

// f64_max isn't directly available in Zig 0.14.0, use std.math.floatMax instead
const INF = std.math.floatMax(f64);

const Edge = struct {
    u: usize,
    v: usize,
    weight: f64,
};

const VertexAndWeight = struct {
    v: usize,
    weight: f64,
};

const WeightAndVertex = struct {
    weight: f64,
    vertex: usize,
    
    pub fn compare(context: void, a: WeightAndVertex, b: WeightAndVertex) std.math.Order {
        _ = context;
        // For min-heap, we want to return Less when a is greater (reversed order)
        return std.math.order(b.weight, a.weight);
    }
};

pub fn johnsonsAlgorithm(allocator: Allocator, graph: []const []const f64) !?ArrayList(ArrayList(f64)) {
    const vertex_count = graph.len;
    var original_edges = ArrayList(Edge).init(allocator);
    defer original_edges.deinit();

    // Step 0: Build a list of edges for the original graph
    for (0..vertex_count) |i| {
        for (0..vertex_count) |j| {
            const weight = graph[i][j];
            if (i == j) {
                if (weight != 0.0) {
                    std.debug.print("Warning: graph[i][i] for i = {d} is {d}, expected to be 0.0, resetting it to 0.0\n", .{ i, weight });
                }
            } else if (weight != INF) {
                try original_edges.append(Edge{ .u = i, .v = j, .weight = weight });
            }
        }
    }

    // Step 1: Form the augmented graph
    // Add a new vertex with index 'vertex_count' and having 0-weight edges to all the original vertices
    var augmented_edges = ArrayList(Edge).init(allocator);
    defer augmented_edges.deinit();
    
    try augmented_edges.appendSlice(original_edges.items);
    for (0..vertex_count) |i| {
        try augmented_edges.append(Edge{ .u = vertex_count, .v = i, .weight = 0.0 });
    }

    // Step 2: Invoke the Bellman-Ford Algorithm starting from the new vertex
    const h_values_opt = try bellmanFordAlgorithm(allocator, vertex_count + 1, 
                                               augmented_edges.items, vertex_count);
    
    if (h_values_opt == null) {
        return null; // A negative cycle was detected by the Bellman-Ford Algorithm
    }
    
    var h_values = h_values_opt.?;
    defer h_values.deinit();
    
    // Remove the value for the augmented vertex
    _ = h_values.pop();

    // Step 3: Reweight the edges
    var reweighted_adjacencies = AutoHashMap(usize, ArrayList(VertexAndWeight)).init(allocator);
    defer {
        var it = reweighted_adjacencies.valueIterator();
        while (it.next()) |value| {
            value.deinit();
        }
        reweighted_adjacencies.deinit();
    }
    
    for (0..vertex_count) |v| {
        try reweighted_adjacencies.put(v, ArrayList(VertexAndWeight).init(allocator));
    }

    for (original_edges.items) |edge| {
        // Ensure the 'values' are valid before reweighting
        if (h_values.items[edge.u] == INF or h_values.items[edge.v] == INF) {
            std.debug.print("Warning: invalid hValues detected by the Bellman-Ford Algorithm.\n", .{});
        }

        const reweight = edge.weight + h_values.items[edge.u] - h_values.items[edge.v];
        try reweighted_adjacencies.getPtr(edge.u).?.append(VertexAndWeight{ .v = edge.v, .weight = reweight });
    }

    // Step 4: Invoke Dijkstra's Algorithm starting from each vertex on the reweighted graph
    var all_pairs_shortest_paths = ArrayList(ArrayList(f64)).init(allocator);
    var error_occurred = false;
    
    for (0..vertex_count) |u| {
        if (error_occurred) break;
        
        // We create a new function that returns a result to handle errors
        if (try dijkstraAlgorithm(allocator, vertex_count, &reweighted_adjacencies, u, h_values.items)) |distances| {
            try all_pairs_shortest_paths.append(distances);
        } else {
            error_occurred = true;
            
            // Clean up previously allocated lists
            for (all_pairs_shortest_paths.items) |*path| {
                path.deinit();
            }
            all_pairs_shortest_paths.deinit();
            
            return null;
        }
    }

    // Step 5: Return the result matrix
    return all_pairs_shortest_paths;
}

fn bellmanFordAlgorithm(
    allocator: Allocator, 
    augmented_vertex_count: usize, 
    edges: []const Edge, 
    source_vertex: usize
) !?ArrayList(f64) {
    var distances = ArrayList(f64).init(allocator);
    try distances.resize(augmented_vertex_count);
    
    for (distances.items, 0..) |*dist, i| {
        dist.* = if (i == source_vertex) 0.0 else INF;
    }

    // Relax the edges (augmented_vertex_count - 1) times
    var updated = true;
    for (0..augmented_vertex_count - 1) |_| {
        if (!updated) break;
        updated = false;
        
        for (edges) |edge| {
            if (distances.items[edge.u] != INF and 
                distances.items[edge.u] + edge.weight < distances.items[edge.v]) {
                distances.items[edge.v] = distances.items[edge.u] + edge.weight;
                updated = true;
            }
        }
    }

    // Check for negative cycles in the graph
    for (edges) |edge| {
        if (distances.items[edge.u] != INF and 
            distances.items[edge.u] + edge.weight < distances.items[edge.v]) {
            distances.deinit();
            return null; // Indicates a negative cycle has been detected
        }
    }

    return distances;
}

fn dijkstraAlgorithm(
    allocator: Allocator,
    vertex_count: usize,
    reweighted_adjacencies: *const AutoHashMap(usize, ArrayList(VertexAndWeight)),
    source_vertex: usize,
    values: []const f64,
) !?ArrayList(f64) {
    var distances = ArrayList(f64).init(allocator);
    try distances.resize(vertex_count);
    
    for (distances.items, 0..) |*dist, i| {
        dist.* = if (i == source_vertex) 0.0 else INF;
    }

    var priority_queue = PriorityQueue(WeightAndVertex, void, WeightAndVertex.compare).init(allocator, {});
    defer priority_queue.deinit();
    
    try priority_queue.add(WeightAndVertex{ .weight = 0.0, .vertex = source_vertex });

    var final_distances = ArrayList(f64).init(allocator);
    try final_distances.resize(vertex_count);
    
    for (final_distances.items) |*dist| {
        dist.* = INF;
    }

    while (priority_queue.removeOrNull()) |weight_and_vertex| {
        const vertex = weight_and_vertex.vertex;
        if (weight_and_vertex.weight > distances.items[vertex]) {
            continue;
        }

        // Store the final shortest path distance, translated back to the distance in the original graph
        if (final_distances.items[vertex] == INF) {
            if (distances.items[vertex] == INF) {
                // This should not happen, but is included as a safety check
                final_distances.items[vertex] = INF;
            } else {
                // Translate distance back to its original weight: d(u,v) = d'(u,v) - h[u] + h[v]
                final_distances.items[vertex] = distances.items[vertex] - values[source_vertex] + values[vertex];
            }
        }

        // Relax the edges outgoing from vertex
        if (reweighted_adjacencies.get(vertex)) |adjacencies| {
            for (adjacencies.items) |pair| {
                if (distances.items[vertex] != INF and 
                    distances.items[vertex] + pair.weight < distances.items[pair.v]) {
                    distances.items[pair.v] = distances.items[vertex] + pair.weight;
                    try priority_queue.add(WeightAndVertex{ .weight = distances.items[pair.v], .vertex = pair.v });
                }
            }
        }
    }

    // Translate distance back to its original weight for any remaining reachable vertices
    for (0..vertex_count) |i| {
        if (final_distances.items[i] == INF and distances.items[i] != INF) {
            final_distances.items[i] = distances.items[i] - values[source_vertex] + values[i];
        }
    }

    distances.deinit();
    return final_distances;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // The element (i, j) is the weight of the edge from vertex i to vertex j.
    // INF, for infinity, means that there is no edge from vertex i to vertex j.
    const graph = [_][]const f64{
        &[_]f64{ 0.0, -5.0, 2.0, 3.0 },
        &[_]f64{ INF, 0.0, 4.0, INF },
        &[_]f64{ INF, INF, 0.0, 1.0 },
        &[_]f64{ INF, INF, INF, 0.0 },
    };

    if (try johnsonsAlgorithm(allocator, &graph)) |result| {
        defer {
            for (result.items) |*row| {
                row.deinit();
            }
            result.deinit();
        }
        
        std.debug.print("All pairs shortest paths:\n", .{});
        std.debug.print("The element (i, j) is the shortest path between vertex i and vertex j.\n", .{});
        
        for (result.items) |row| {
            std.debug.print("[", .{});
            for (row.items) |number| {
                if (number == INF) {
                    std.debug.print("INF ", .{});
                } else {
                    std.debug.print("{d} ", .{number});
                }
            }
            std.debug.print("]\n", .{});
        }
    } else {
        std.debug.print("A negative cycle was detected in the graph.\n", .{});
    }
}
Output:
All pairs shortest paths:
The element (i, j) is the shortest path between vertex i and vertex j.
[0 -5 2 3 ]
[INF 0 4 5 ]
[INF INF 0 1 ]
[INF INF INF 0 ]
Cookies help us deliver our services. By using our services, you agree to our use of cookies.