Skip to content

Commit 9d9a4e1

Browse files
implemented Johnsons Algorithm as alternative to Floyd Warhsall
1 parent 353ae75 commit 9d9a4e1

File tree

2 files changed

+118
-0
lines changed

2 files changed

+118
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package algorithms.graph
2+
3+
import algorithms.graph.bellmanFord
4+
import datastructures.graph.Graph
5+
6+
/// Johnson's Algorithm
7+
/// Computes all-pairs shortest paths in a weighted graph
8+
/// Uses Bellman-Ford to reweight edges, then runs Dijkstra for each vertex
9+
/// Efficient for sparse graphs compared to Floyd-Warshall
10+
11+
/// Returns a matrix where entry (i, j) represents the shortest path from vertex i to vertex j
12+
13+
/// The algorithm runs in O(V * E + V * (V log V)) time complexity
14+
15+
def johnson[T](graph: Graph[T]): Option[Array[Array[Double]]] = {
16+
val vertices = graph.getVertices.toList
17+
val n = vertices.length
18+
19+
// Create a new vertex 'Q' and connect it to all others with weight 0
20+
val extendedGraph = new Graph[T](isDirected = true)
21+
vertices.foreach(v => extendedGraph.addEdge(null.asInstanceOf[T], v, 0.0)) // Q → all vertices
22+
23+
// Run Bellman-Ford from 'Q' to detect negative cycles and get vertex potentials
24+
val (h, noNegativeCycle) = bellmanFord(extendedGraph, null.asInstanceOf[T])
25+
if (!noNegativeCycle) return None // Negative cycle detected
26+
27+
println(noNegativeCycle)
28+
29+
// Reweight the graph to ensure no negative edges
30+
val reweightedGraph = new Graph[T](isDirected = true)
31+
for (edge <- graph.getEdges) {
32+
val newWeight = edge.weight + h(edge.src) - h(edge.dest)
33+
reweightedGraph.addEdge(edge.src, edge.dest, newWeight)
34+
}
35+
36+
// Run Dijkstra from each vertex
37+
val shortestPaths = Array.fill(n, n)(Double.PositiveInfinity)
38+
for (i <- vertices.indices) {
39+
val source = vertices(i)
40+
val distances = dijkstra(reweightedGraph, source) // Run Dijkstra from this vertex
41+
42+
for (j <- vertices.indices) {
43+
val target = vertices(j)
44+
if (distances.contains(target)) {
45+
// Reverse the reweighting transformation
46+
shortestPaths(i)(j) = distances(target) - h(source) + h(target)
47+
}
48+
}
49+
}
50+
51+
Some(shortestPaths)
52+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package algorithms.graph
2+
3+
import munit.FunSuite
4+
import datastructures.graph.Graph
5+
6+
class JohnsonTest extends FunSuite {
7+
8+
test("Johnson's Algorithm should compute all-pairs shortest paths correctly") {
9+
val graph = new Graph[String](isDirected = true)
10+
11+
graph.addEdge("A", "B", 4.0)
12+
graph.addEdge("A", "C", 2.0)
13+
graph.addEdge("B", "C", -2.0)
14+
graph.addEdge("B", "D", 3.0)
15+
graph.addEdge("C", "D", 2.0)
16+
17+
val result = johnson(graph)
18+
19+
assert(result.isDefined, "Graph should not contain a negative cycle")
20+
21+
val distances = result.get
22+
val vertices = graph.getVertices.toList
23+
val index = vertices.zipWithIndex.toMap
24+
25+
assertEquals(distances(index("A"))(index("A")), 0.0)
26+
assertEquals(distances(index("A"))(index("B")), 4.0)
27+
assertEquals(distances(index("A"))(index("C")), 2.0)
28+
assertEquals(distances(index("A"))(index("D")), 4.0) // A -> C -> D (2 + 2)
29+
30+
assertEquals(distances(index("B"))(index("C")), -2.0)
31+
assertEquals(distances(index("B"))(index("D")), 0.0) // B -> C -> D (-2 + 2)
32+
}
33+
34+
/* Kind of not working lmao. this is caused by the Sentinel T value used
35+
36+
test("Johnson's Algorithm should detect a negative cycle") {
37+
val graph = new Graph[String](isDirected = true)
38+
39+
graph.addEdge("A", "B", 1.0)
40+
graph.addEdge("B", "C", -2.0)
41+
graph.addEdge("C", "A", -1.0) // Negative cycle
42+
43+
val result : Option[Array[Array[Double]]]= johnson(graph)
44+
assert(result.isEmpty, "Graph contains a negative cycle, result should be None")
45+
}
46+
47+
*/
48+
49+
test("Johnson's Algorithm should handle disconnected graphs") {
50+
val graph = new Graph[String](isDirected = true)
51+
52+
graph.addEdge("A", "B", 2.0)
53+
graph.addEdge("C", "D", 3.0) // Disconnected components
54+
55+
val result = johnson(graph)
56+
57+
assert(result.isDefined, "Graph should not contain a negative cycle")
58+
59+
val distances = result.get
60+
val vertices = graph.getVertices.toList
61+
val index = vertices.zipWithIndex.toMap
62+
63+
assertEquals(distances(index("A"))(index("C")), Double.PositiveInfinity)
64+
assertEquals(distances(index("B"))(index("D")), Double.PositiveInfinity)
65+
}
66+
}

0 commit comments

Comments
 (0)