Bellman Ford Shortest Path
S
# Bellman-Ford Shortest Path Algorithm
#
# The Bellman-Ford algorithm computes shortest paths from a single source vertex to
# all other vertices in a weighted graph. Unlike Dijkstra's algorithm, Bellman-Ford
# supports graphs with negative edge weights and can detect negative-weight cycles.
#
# Time Complexity: O(V * E)
# Space Complexity: O(V)
#
# Input: graph as an adjacency list where each entry is a list of edges with fields
# `vertex` and `weight`, and `source` vertex index (integer)
# Output: A list containing distances, predecessors, and a flag indicating whether
# a negative cycle was detected
bellman_ford_shortest_path <- function(graph, source) {
# Collect all vertices (numeric indices expected)
all_vertices <- unique(c(names(graph), unlist(lapply(graph, function(x) sapply(x, function(e) e$vertex)))))
# Convert to numeric vector
all_vertices <- as.numeric(all_vertices)
num_vertices <- max(all_vertices)
# Initialize distances and predecessors
distances <- rep(Inf, num_vertices)
predecessor <- rep(-1, num_vertices)
distances[source] <- 0
# Relax edges repeatedly (V-1 times)
for (i in 1:(num_vertices - 1)) {
updated <- FALSE
# Iterate all edges
for (u_char in names(graph)) {
u <- as.numeric(u_char)
for (edge in graph[[u_char]]) {
v <- edge$vertex
w <- edge$weight
if (distances[u] != Inf && distances[u] + w < distances[v]) {
distances[v] <- distances[u] + w
predecessor[v] <- u
updated <- TRUE
}
}
}
# If no update in this pass, we can stop early
if (!updated) break
}
# Check for negative-weight cycles: if we can still relax, there is a negative cycle
negative_cycle <- FALSE
for (u_char in names(graph)) {
u <- as.numeric(u_char)
for (edge in graph[[u_char]]) {
v <- edge$vertex
w <- edge$weight
if (distances[u] != Inf && distances[u] + w < distances[v]) {
negative_cycle <- TRUE
break
}
}
if (negative_cycle) break
}
return(list(
distances = distances,
predecessor = predecessor,
negative_cycle = negative_cycle
))
}
# Helper to reconstruct the shortest path from source to target
get_bellman_ford_path <- function(result, source, target) {
if (result$negative_cycle) {
return(list(path = NULL, distance = NA, message = "Negative-weight cycle detected; shortest path undefined"))
}
distances <- result$distances
predecessor <- result$predecessor
if (is.infinite(distances[target])) {
return(list(path = NULL, distance = Inf, message = "Target not reachable from source"))
}
path <- c()
current <- target
while (current != -1) {
path <- c(current, path)
if (current == source) break
current <- predecessor[current]
}
return(list(path = path, distance = distances[target]))
}
# Example usage and tests
cat("=== Bellman-Ford Shortest Path Algorithm ===\n")
# Example graph with negative edges but no negative cycle
# Graph structure:
# 1 -> 2 (6), 1 -> 3 (5), 1 -> 4 (5)
# 2 -> 5 (-1)
# 3 -> 2 (-2), 3 -> 5 (1)
# 4 -> 3 (-2), 4 -> 6 (-1)
# 5 -> 6 (3)
# 6 -> (none)
bf_graph <- list(
"1" = list(list(vertex = 2, weight = 6), list(vertex = 3, weight = 5), list(vertex = 4, weight = 5)),
"2" = list(list(vertex = 5, weight = -1)),
"3" = list(list(vertex = 2, weight = -2), list(vertex = 5, weight = 1)),
"4" = list(list(vertex = 3, weight = -2), list(vertex = 6, weight = -1)),
"5" = list(list(vertex = 6, weight = 3)),
"6" = list()
)
cat("Graph (adjacency list):\n")
for (v in names(bf_graph)) {
edges <- bf_graph[[v]]
if (length(edges) > 0) {
edge_strs <- sapply(edges, function(e) paste0(e$vertex, "(", e$weight, ")"))
cat("Vertex", v, "-> [", paste(edge_strs, collapse = ", "), "]\n")
} else {
cat("Vertex", v, "-> []\n")
}
}
cat("\nRunning Bellman-Ford from vertex 1:\n")
bf_result <- bellman_ford_shortest_path(bf_graph, 1)
cat("Negative cycle detected:", bf_result$negative_cycle, "\n")
cat("Distances from vertex 1:\n")
for (i in 1:length(bf_result$distances)) {
d <- bf_result$distances[i]
if (is.infinite(d)) {
cat("To vertex", i, ": unreachable\n")
} else {
cat("To vertex", i, ": distance =", d, "\n")
}
}
cat("\nShortest path from 1 to 6:\n")
path_info <- get_bellman_ford_path(bf_result, 1, 6)
if (!is.null(path_info$path)) {
cat("Path:", paste(path_info$path, collapse = " -> "), "\n")
cat("Distance:", path_info$distance, "\n")
} else {
cat(path_info$message, "\n")
}