Python/graphs/bellman_ford.py

115 lines
3.9 KiB
Python

from typing import NamedTuple
class Edge(NamedTuple):
src: int
dst: int
weight: int
def print_distance_and_paths(distance: list[float], paths: list[list[int]], src: int):
"""
Prints the shortest distance and paths from the source vertex to each vertex.
"""
print(f"Vertex\tShortest Distance from Vertex {src}\tPath")
for vertex, (dist, path) in enumerate(zip(distance, paths)):
path_str = " -> ".join(map(str, path)) if path else "No path"
print(f"{vertex}\t\t{dist}\t\t\t\t{path_str}")
def check_negative_cycle(graph: list[Edge], distance: list[float],
predecessor: list[int]) -> bool:
"""
Checks if there is a negative weight cycle reachable from the source vertex.
If found, return True, indicating a negative cycle.
"""
for edge in graph:
if (distance[edge.src] != float("inf") and
distance[edge.src] + edge.weight
< distance[edge.dst]):
# Update predecessors to indicate a cycle for affected paths
# Use -1 as a marker for negative cycle detection
predecessor[edge.dst] = -1
return True
return False
def reconstruct_paths(predecessor: list[int],
vertex_count: int, src: int) -> list[list[int]]:
"""
Reconstructs the shortest paths from the source vertex to
each vertex using the predecessor list.
"""
paths = [[] for _ in range(vertex_count)]
for vertex in range(vertex_count):
if predecessor[vertex] == -1:
paths[vertex] = ["Negative cycle detected"]
elif predecessor[vertex] is not None:
path = []
current = vertex
while current is not None:
path.insert(0, current)
if current == src:
break
current = predecessor[current]
paths[vertex] = path
return paths
def bellman_ford(graph: list[Edge],
vertex_count: int, src: int) -> tuple[list[float], list[list[int]]]:
"""
Returns the shortest paths from a vertex src to all other vertices,
including path reconstruction.
"""
distance = [float("inf")] * vertex_count
predecessor = [None] * vertex_count # Keeps track of the path predecessors
distance[src] = 0.0
# Step 1: Relax edges repeatedly
for _ in range(vertex_count - 1):
for edge in graph:
if (distance[edge.src] != float("inf") and
distance[edge.src] + edge.weight < distance[edge.dst]):
distance[edge.dst] = distance[edge.src] + edge.weight
predecessor[edge.dst] = edge.src
# Step 2: Check for negative weight cycles
if check_negative_cycle(graph, distance, predecessor):
raise Exception("Negative cycle found")
# Step 3: Reconstruct paths from predecessor list
paths = reconstruct_paths(predecessor, vertex_count, src)
return distance, paths
if __name__ == "__main__":
import doctest
doctest.testmod()
try:
V = int(input("Enter number of vertices: ").strip())
E = int(input("Enter number of edges: ").strip())
graph: list[Edge] = []
for i in range(E):
print(f"Edge {i + 1}")
src, dest, weight = map(int,
input("Enter source, destination, weight: ").strip().split())
if src < 0 or src >= V or dest < 0 or dest >= V:
print(f"Invalid vertices: src and dest should be between 0 and {V - 1}")
continue
graph.append(Edge(src, dest, weight))
source = int(input("\nEnter shortest path source vertex: ").strip())
if source < 0 or source >= V:
print(f"Invalid source: source should be between 0 and {V - 1}")
else:
shortest_distance, paths = bellman_ford(graph, V, source)
print_distance_and_paths(shortest_distance, paths, source)
except ValueError:
print("Invalid input. Please enter valid integers.")