Card 05/ 11
Bellman-Ford in Python, Java and TypeScript
Passes over an edge list, stopping early when a pass changes nothing, and a final check for a negative cycle.
Bellman-Ford needs no adjacency lists and no queue: a list of edges, a distance per vertex, and two loops. The listings below run on the topic's graph and on the same graph with E->C of weight −6 added, and print the distances, or the negative cycle, with the number of passes run.
Bellman-Ford with an early stop and a negative-cycle check, in each language
import math
def bellman_ford(vertices, edges, source):
distance = {vertex: math.inf for vertex in vertices}
distance[source] = 0
passes = 0
for _ in range(len(vertices) - 1):
passes += 1
changed = False
for u, v, weight in edges:
if distance[u] + weight < distance[v]:
distance[v] = distance[u] + weight
changed = True
if not changed:
break
for u, v, weight in edges:
if distance[u] + weight < distance[v]:
return None, passes
return distance, passes
VERTICES = ["A", "B", "C", "D", "E"]
EDGES = [("A", "B", 2), ("A", "C", 7), ("B", "C", 3), ("B", "D", 8), ("C", "D", 2), ("C", "E", 6), ("D", "E", 1)]
for name, edges in (("topic graph", EDGES), ("with E->C -6", EDGES + [("E", "C", -6)])):
distance, passes = bellman_ford(VERTICES, edges, "A")
if distance is None:
print(f"{name}: negative cycle, found after {passes} passes")
else:
print(f"{name}: {', '.join(f'{v} {distance[v]}' for v in VERTICES)} after {passes} passes")topic graph: A 0, B 2, C 5, D 7, E 8 after 2 passes
with E->C -6: negative cycle, found after 4 passesimport java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BellmanCode {
record Edge(String from, String to, int weight) {}
record Result(Map<String, Integer> distance, int passes, boolean negativeCycle) {}
static final int INF = Integer.MAX_VALUE;
static boolean relaxes(Map<String, Integer> distance, Edge edge) {
int from = distance.get(edge.from());
return from != INF && from + edge.weight() < distance.get(edge.to());
}
static Result bellmanFord(List<String> vertices, List<Edge> edges, String source) {
Map<String, Integer> distance = new HashMap<>();
for (String vertex : vertices) distance.put(vertex, INF);
distance.put(source, 0);
int passes = 0;
for (int round = 0; round < vertices.size() - 1; round++) {
passes++;
boolean changed = false;
for (Edge edge : edges) {
if (relaxes(distance, edge)) {
distance.put(edge.to(), distance.get(edge.from()) + edge.weight());
changed = true;
}
}
if (!changed) break;
}
boolean negativeCycle = edges.stream().anyMatch(edge -> relaxes(distance, edge));
return new Result(distance, passes, negativeCycle);
}
public static void main(String[] args) {
List<String> vertices = List.of("A", "B", "C", "D", "E");
List<Edge> edges = List.of(new Edge("A", "B", 2), new Edge("A", "C", 7), new Edge("B", "C", 3), new Edge("B", "D", 8),
new Edge("C", "D", 2), new Edge("C", "E", 6), new Edge("D", "E", 1));
List<Edge> withCycle = new ArrayList<>(edges);
withCycle.add(new Edge("E", "C", -6));
Map<String, List<Edge>> graphs = Map.of("topic graph", edges, "with E->C -6", withCycle);
for (String name : List.of("topic graph", "with E->C -6")) {
Result result = bellmanFord(vertices, graphs.get(name), "A");
if (result.negativeCycle()) {
System.out.println(name + ": negative cycle, found after " + result.passes() + " passes");
} else {
List<String> parts = new ArrayList<>();
for (String vertex : vertices) parts.add(vertex + " " + result.distance().get(vertex));
System.out.println(name + ": " + String.join(", ", parts) + " after " + result.passes() + " passes");
}
}
}
}topic graph: A 0, B 2, C 5, D 7, E 8 after 2 passes
with E->C -6: negative cycle, found after 4 passestype Edge = [string, string, number];
function bellmanFord(vertices: string[], edges: Edge[], source: string): { distance: Map<string, number> | null; passes: number } {
const distance = new Map<string, number>(vertices.map((v) => [v, Infinity]));
distance.set(source, 0);
let passes = 0;
for (let round = 0; round < vertices.length - 1; round++) {
passes++;
let changed = false;
for (const [u, v, weight] of edges) {
if (distance.get(u)! + weight < distance.get(v)!) {
distance.set(v, distance.get(u)! + weight);
changed = true;
}
}
if (!changed) break;
}
const negativeCycle = edges.some(([u, v, weight]) => distance.get(u)! + weight < distance.get(v)!);
return { distance: negativeCycle ? null : distance, passes };
}
const vertices = ["A", "B", "C", "D", "E"];
const edges: Edge[] = [["A", "B", 2], ["A", "C", 7], ["B", "C", 3], ["B", "D", 8], ["C", "D", 2], ["C", "E", 6], ["D", "E", 1]];
const graphs: [string, Edge[]][] = [["topic graph", edges], ["with E->C -6", [...edges, ["E", "C", -6]]]];
for (const [name, graphEdges] of graphs) {
const { distance, passes } = bellmanFord(vertices, graphEdges, "A");
if (distance === null) console.log(`${name}: negative cycle, found after ${passes} passes`);
else console.log(`${name}: ${vertices.map((v) => `${v} ${distance.get(v)}`).join(", ")} after ${passes} passes`);
}topic graph: A 0, B 2, C 5, D 7, E 8 after 2 passes
with E->C -6: negative cycle, found after 4 passesAll three printed the same two lines. The highlighted lines are the bound of V − 1 passes, the early stop when a pass changes nothing, and the extra pass that looks for a negative cycle.
The topic's graph stopped after 2 passes, where the bound allowed 4. The edges are listed in the order A->B, A->C, B->C and so on, so each edge's tail is set before the edge is reached and the first pass sets every distance; the second pass changes nothing and ends the loop. With the edges reversed the same program would run all four passes, as the trace on Bellman-Ford: Relaxing Every Edge V − 1 Times shows.
The graph with the negative cycle used all four passes, because every pass lowered something on the cycle, and the check still found an edge to relax.
Infinity is math.inf in Python and Infinity in TypeScript, and both stay infinite when a weight is added, so an unreached vertex can never relax an edge. Java's Integer.MAX_VALUE plus a weight would overflow to a negative number, so relaxes checks for it first.
← → move through sections and cards