Skip to content

Commit 5a1b873

Browse files
authored
feat(graph): Add Bellman-Ford algorithm with negative cycle detection
This PR implements the Bellman-Ford algorithm for finding shortest paths in weighted directed graphs. Key Features: ✅ Handles negative weight edges (unlike Dijkstra) ✅ Detects negative weight cycles ✅ Comprehensive JavaDoc documentation ✅ Clean API with Result class ✅ Path reconstruction support ✅ Input validation ✅ Early termination optimization Time Complexity: O(V × E) Space Complexity: O(V) This implementation fills a gap in the graph algorithms collection and complements existing shortest path implementations.
1 parent 2c4bf3c commit 5a1b873

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.List;
6+
7+
/**
8+
* Implementation of the Bellman-Ford algorithm for finding shortest paths from a single source
9+
* vertex to all other vertices in a weighted directed graph. Unlike Dijkstra's algorithm,
10+
* Bellman-Ford can handle graphs with negative weight edges and can detect negative weight cycles.
11+
*
12+
* <p>Time Complexity: O(V * E) where V is the number of vertices and E is the number of edges
13+
* Space Complexity: O(V)
14+
*
15+
* <p>Algorithm Steps:
16+
* 1. Initialize distances from source to all vertices as infinite and distance to source as 0
17+
* 2. Relax all edges V-1 times (where V is the number of vertices)
18+
* 3. Check for negative weight cycles by attempting one more relaxation
19+
*
20+
* @author vardhan30016
21+
* @see <a href="https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm">Bellman-Ford Algorithm</a>
22+
*/
23+
public final class BellmanFord {
24+
25+
private BellmanFord() {
26+
throw new UnsupportedOperationException("Utility class");
27+
}
28+
29+
/**
30+
* Represents a weighted edge in the graph.
31+
*/
32+
public static class Edge {
33+
private final int source;
34+
private final int destination;
35+
private final int weight;
36+
37+
/**
38+
* Creates a new edge.
39+
*
40+
* @param source the source vertex
41+
* @param destination the destination vertex
42+
* @param weight the weight of the edge
43+
*/
44+
public Edge(int source, int destination, int weight) {
45+
this.source = source;
46+
this.destination = destination;
47+
this.weight = weight;
48+
}
49+
50+
public int getSource() {
51+
return source;
52+
}
53+
54+
public int getDestination() {
55+
return destination;
56+
}
57+
58+
public int getWeight() {
59+
return weight;
60+
}
61+
}
62+
63+
/**
64+
* Represents the result of the Bellman-Ford algorithm.
65+
*/
66+
public static class Result {
67+
private final int[] distances;
68+
private final int[] predecessors;
69+
private final boolean hasNegativeCycle;
70+
71+
/**
72+
* Creates a new result.
73+
*
74+
* @param distances array of shortest distances from source to each vertex
75+
* @param predecessors array of predecessor vertices in shortest paths
76+
* @param hasNegativeCycle true if the graph contains a negative weight cycle
77+
*/
78+
public Result(int[] distances, int[] predecessors, boolean hasNegativeCycle) {
79+
this.distances = Arrays.copyOf(distances, distances.length);
80+
this.predecessors = Arrays.copyOf(predecessors, predecessors.length);
81+
this.hasNegativeCycle = hasNegativeCycle;
82+
}
83+
84+
/**
85+
* Gets the shortest distance to a vertex.
86+
*
87+
* @param vertex the target vertex
88+
* @return the shortest distance from source to the vertex, or Integer.MAX_VALUE if unreachable
89+
*/
90+
public int getDistance(int vertex) {
91+
return distances[vertex];
92+
}
93+
94+
/**
95+
* Gets all distances.
96+
*
97+
* @return array of distances from source to all vertices
98+
*/
99+
public int[] getDistances() {
100+
return Arrays.copyOf(distances, distances.length);
101+
}
102+
103+
/**
104+
* Gets the shortest path to a vertex.
105+
*
106+
* @param vertex the target vertex
107+
* @return list of vertices in the shortest path from source to target
108+
* @throws IllegalStateException if the graph contains a negative cycle
109+
* @throws IllegalArgumentException if the vertex is unreachable
110+
*/
111+
public List<Integer> getPath(int vertex) {
112+
if (hasNegativeCycle) {
113+
throw new IllegalStateException("Graph contains a negative weight cycle");
114+
}
115+
if (distances[vertex] == Integer.MAX_VALUE) {
116+
throw new IllegalArgumentException("Vertex " + vertex + " is unreachable from source");
117+
}
118+
119+
List<Integer> path = new ArrayList<>();
120+
for (int v = vertex; v != -1; v = predecessors[v]) {
121+
path.add(0, v);
122+
}
123+
return path;
124+
}
125+
126+
/**
127+
* Checks if the graph contains a negative weight cycle.
128+
*
129+
* @return true if a negative cycle exists, false otherwise
130+
*/
131+
public boolean hasNegativeCycle() {
132+
return hasNegativeCycle;
133+
}
134+
}
135+
136+
/**
137+
* Finds shortest paths from a source vertex to all other vertices using the Bellman-Ford algorithm.
138+
*
139+
* @param vertices the number of vertices in the graph
140+
* @param edges list of edges in the graph
141+
* @param source the source vertex (0-indexed)
142+
* @return Result object containing distances, paths, and negative cycle information
143+
* @throws IllegalArgumentException if vertices is non-positive or source is invalid
144+
*/
145+
public static Result findShortestPaths(int vertices, List<Edge> edges, int source) {
146+
if (vertices <= 0) {
147+
throw new IllegalArgumentException("Number of vertices must be positive");
148+
}
149+
if (source < 0 || source >= vertices) {
150+
throw new IllegalArgumentException("Source vertex is out of bounds");
151+
}
152+
153+
// Step 1: Initialize distances and predecessors
154+
int[] distances = new int[vertices];
155+
int[] predecessors = new int[vertices];
156+
Arrays.fill(distances, Integer.MAX_VALUE);
157+
Arrays.fill(predecessors, -1);
158+
distances[source] = 0;
159+
160+
// Step 2: Relax all edges V-1 times
161+
for (int i = 0; i < vertices - 1; i++) {
162+
boolean updated = false;
163+
for (Edge edge : edges) {
164+
if (distances[edge.getSource()] != Integer.MAX_VALUE) {
165+
int newDistance = distances[edge.getSource()] + edge.getWeight();
166+
if (newDistance < distances[edge.getDestination()]) {
167+
distances[edge.getDestination()] = newDistance;
168+
predecessors[edge.getDestination()] = edge.getSource();
169+
updated = true;
170+
}
171+
}
172+
}
173+
// Early termination optimization: if no updates in this iteration, we're done
174+
if (!updated) {
175+
break;
176+
}
177+
}
178+
179+
// Step 3: Check for negative weight cycles
180+
boolean hasNegativeCycle = false;
181+
for (Edge edge : edges) {
182+
if (distances[edge.getSource()] != Integer.MAX_VALUE) {
183+
int newDistance = distances[edge.getSource()] + edge.getWeight();
184+
if (newDistance < distances[edge.getDestination()]) {
185+
hasNegativeCycle = true;
186+
break;
187+
}
188+
}
189+
}
190+
191+
return new Result(distances, predecessors, hasNegativeCycle);
192+
}
193+
}

0 commit comments

Comments
 (0)