diff --git a/src/main/java/com/thealgorithms/graph/BellmanFord.java b/src/main/java/com/thealgorithms/graph/BellmanFord.java new file mode 100644 index 000000000000..f87b456a7976 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/BellmanFord.java @@ -0,0 +1,193 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Implementation of the Bellman-Ford algorithm for finding shortest paths from a single source + * vertex to all other vertices in a weighted directed graph. Unlike Dijkstra's algorithm, + * Bellman-Ford can handle graphs with negative weight edges and can detect negative weight cycles. + * + *

Time Complexity: O(V * E) where V is the number of vertices and E is the number of edges + * Space Complexity: O(V) + * + *

Algorithm Steps: + * 1. Initialize distances from source to all vertices as infinite and distance to source as 0 + * 2. Relax all edges V-1 times (where V is the number of vertices) + * 3. Check for negative weight cycles by attempting one more relaxation + * + * @author vardhan30016 + * @see Bellman-Ford Algorithm + */ +public final class BellmanFord { + + private BellmanFord() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Represents a weighted edge in the graph. + */ + public static class Edge { + private final int source; + private final int destination; + private final int weight; + + /** + * Creates a new edge. + * + * @param source the source vertex + * @param destination the destination vertex + * @param weight the weight of the edge + */ + public Edge(int source, int destination, int weight) { + this.source = source; + this.destination = destination; + this.weight = weight; + } + + public int getSource() { + return source; + } + + public int getDestination() { + return destination; + } + + public int getWeight() { + return weight; + } + } + + /** + * Represents the result of the Bellman-Ford algorithm. + */ + public static class Result { + private final int[] distances; + private final int[] predecessors; + private final boolean hasNegativeCycle; + + /** + * Creates a new result. + * + * @param distances array of shortest distances from source to each vertex + * @param predecessors array of predecessor vertices in shortest paths + * @param hasNegativeCycle true if the graph contains a negative weight cycle + */ + public Result(int[] distances, int[] predecessors, boolean hasNegativeCycle) { + this.distances = Arrays.copyOf(distances, distances.length); + this.predecessors = Arrays.copyOf(predecessors, predecessors.length); + this.hasNegativeCycle = hasNegativeCycle; + } + + /** + * Gets the shortest distance to a vertex. + * + * @param vertex the target vertex + * @return the shortest distance from source to the vertex, or Integer.MAX_VALUE if unreachable + */ + public int getDistance(int vertex) { + return distances[vertex]; + } + + /** + * Gets all distances. + * + * @return array of distances from source to all vertices + */ + public int[] getDistances() { + return Arrays.copyOf(distances, distances.length); + } + + /** + * Gets the shortest path to a vertex. + * + * @param vertex the target vertex + * @return list of vertices in the shortest path from source to target + * @throws IllegalStateException if the graph contains a negative cycle + * @throws IllegalArgumentException if the vertex is unreachable + */ + public List getPath(int vertex) { + if (hasNegativeCycle) { + throw new IllegalStateException("Graph contains a negative weight cycle"); + } + if (distances[vertex] == Integer.MAX_VALUE) { + throw new IllegalArgumentException("Vertex " + vertex + " is unreachable from source"); + } + + List path = new ArrayList<>(); + for (int v = vertex; v != -1; v = predecessors[v]) { + path.add(0, v); + } + return path; + } + + /** + * Checks if the graph contains a negative weight cycle. + * + * @return true if a negative cycle exists, false otherwise + */ + public boolean hasNegativeCycle() { + return hasNegativeCycle; + } + } + + /** + * Finds shortest paths from a source vertex to all other vertices using the Bellman-Ford algorithm. + * + * @param vertices the number of vertices in the graph + * @param edges list of edges in the graph + * @param source the source vertex (0-indexed) + * @return Result object containing distances, paths, and negative cycle information + * @throws IllegalArgumentException if vertices is non-positive or source is invalid + */ + public static Result findShortestPaths(int vertices, Iterable edges, int source) { + if (vertices <= 0) { + throw new IllegalArgumentException("Number of vertices must be positive"); + } + if (source < 0 || source >= vertices) { + throw new IllegalArgumentException("Source vertex is out of bounds"); + } + + // Step 1: Initialize distances and predecessors + int[] distances = new int[vertices]; + int[] predecessors = new int[vertices]; + Arrays.fill(distances, Integer.MAX_VALUE); + Arrays.fill(predecessors, -1); + distances[source] = 0; + + // Step 2: Relax all edges V-1 times + for (int i = 0; i < vertices - 1; i++) { + boolean updated = false; + for (Edge edge : edges) { + if (distances[edge.getSource()] != Integer.MAX_VALUE) { + int newDistance = distances[edge.getSource()] + edge.getWeight(); + if (newDistance < distances[edge.getDestination()]) { + distances[edge.getDestination()] = newDistance; + predecessors[edge.getDestination()] = edge.getSource(); + updated = true; + } + } + } + // Early termination optimization: if no updates in this iteration, we're done + if (!updated) { + break; + } + } + + // Step 3: Check for negative weight cycles + boolean hasNegativeCycle = false; + for (Edge edge : edges) { + if (distances[edge.getSource()] != Integer.MAX_VALUE) { + int newDistance = distances[edge.getSource()] + edge.getWeight(); + if (newDistance < distances[edge.getDestination()]) { + hasNegativeCycle = true; + break; + } + } + } + + return new Result(distances, predecessors, hasNegativeCycle); + } +} diff --git a/src/test/java/com/thealgorithms/graph/BellmanFordTest.java b/src/test/java/com/thealgorithms/graph/BellmanFordTest.java new file mode 100644 index 000000000000..3e57718a660d --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/BellmanFordTest.java @@ -0,0 +1,137 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Test class for Bellman-Ford algorithm implementation + */ +class BellmanFordTest { + + @Test + void testSimpleGraph() { + List edges = new ArrayList<>(); + edges.add(new BellmanFord.Edge(0, 1, 6)); + edges.add(new BellmanFord.Edge(0, 2, 7)); + edges.add(new BellmanFord.Edge(1, 2, 8)); + edges.add(new BellmanFord.Edge(1, 3, 5)); + edges.add(new BellmanFord.Edge(1, 4, -4)); + edges.add(new BellmanFord.Edge(2, 3, -3)); + edges.add(new BellmanFord.Edge(2, 4, 9)); + edges.add(new BellmanFord.Edge(3, 1, -2)); + edges.add(new BellmanFord.Edge(4, 0, 2)); + edges.add(new BellmanFord.Edge(4, 3, 7)); + + BellmanFord.Result result = BellmanFord.findShortestPaths(5, edges, 0); + + assertFalse(result.hasNegativeCycle()); + assertArrayEquals(new int[] {0, 2, 7, 4, -2}, result.getDistances()); + } + + @Test + void testGraphWithNegativeCycle() { + List edges = new ArrayList<>(); + edges.add(new BellmanFord.Edge(0, 1, 1)); + edges.add(new BellmanFord.Edge(1, 2, -3)); + edges.add(new BellmanFord.Edge(2, 0, 1)); + + BellmanFord.Result result = BellmanFord.findShortestPaths(3, edges, 0); + + assertTrue(result.hasNegativeCycle()); + } + + @Test + void testDisconnectedGraph() { + List edges = new ArrayList<>(); + edges.add(new BellmanFord.Edge(0, 1, 5)); + edges.add(new BellmanFord.Edge(2, 3, 2)); + + BellmanFord.Result result = BellmanFord.findShortestPaths(4, edges, 0); + + assertEquals(0, result.getDistance(0)); + assertEquals(5, result.getDistance(1)); + assertEquals(Integer.MAX_VALUE, result.getDistance(2)); + assertEquals(Integer.MAX_VALUE, result.getDistance(3)); + } + + @Test + void testSingleVertex() { + List edges = new ArrayList<>(); + BellmanFord.Result result = BellmanFord.findShortestPaths(1, edges, 0); + + assertFalse(result.hasNegativeCycle()); + assertArrayEquals(new int[] {0}, result.getDistances()); + } + + @Test + void testPathReconstruction() { + List edges = new ArrayList<>(); + edges.add(new BellmanFord.Edge(0, 1, 4)); + edges.add(new BellmanFord.Edge(0, 2, 2)); + edges.add(new BellmanFord.Edge(1, 2, 1)); + edges.add(new BellmanFord.Edge(2, 3, 3)); + + BellmanFord.Result result = BellmanFord.findShortestPaths(4, edges, 0); + + List path = result.getPath(3); + assertNotNull(path); + assertEquals(Arrays.asList(0, 2, 3), path); + } + + @Test + void testNegativeWeights() { + List edges = new ArrayList<>(); + edges.add(new BellmanFord.Edge(0, 1, 5)); + edges.add(new BellmanFord.Edge(1, 2, -2)); + edges.add(new BellmanFord.Edge(2, 3, 3)); + + BellmanFord.Result result = BellmanFord.findShortestPaths(4, edges, 0); + + assertFalse(result.hasNegativeCycle()); + assertEquals(0, result.getDistance(0)); + assertEquals(5, result.getDistance(1)); + assertEquals(3, result.getDistance(2)); + assertEquals(6, result.getDistance(3)); + } + + @Test + void testInvalidSource() { + List edges = new ArrayList<>(); + edges.add(new BellmanFord.Edge(0, 1, 5)); + + assertThrows(IllegalArgumentException.class, () -> { + BellmanFord.findShortestPaths(2, edges, -1); + }); + + assertThrows(IllegalArgumentException.class, () -> { + BellmanFord.findShortestPaths(2, edges, 2); + }); + } + + @Test + void testInvalidVertexCount() { + List edges = new ArrayList<>(); + + assertThrows(IllegalArgumentException.class, () -> { + BellmanFord.findShortestPaths(0, edges, 0); + }); + + assertThrows(IllegalArgumentException.class, () -> { + BellmanFord.findShortestPaths(-1, edges, 0); + }); + } + + @Test + void testNullEdges() { + assertThrows(NullPointerException.class, () -> { + BellmanFord.findShortestPaths(3, null, 0); + }); + } +}