|
| 1 | +# Bellman-Ford Shortest Path Algorithm |
| 2 | +# |
| 3 | +# The Bellman-Ford algorithm computes shortest paths from a single source vertex to |
| 4 | +# all other vertices in a weighted graph. Unlike Dijkstra's algorithm, Bellman-Ford |
| 5 | +# supports graphs with negative edge weights and can detect negative-weight cycles. |
| 6 | +# |
| 7 | +# Time Complexity: O(V * E) |
| 8 | +# Space Complexity: O(V) |
| 9 | +# |
| 10 | +# Input: graph as an adjacency list where each entry is a list of edges with fields |
| 11 | +# `vertex` and `weight`, and `source` vertex index (integer) |
| 12 | +# Output: A list containing distances, predecessors, and a flag indicating whether |
| 13 | +# a negative cycle was detected |
| 14 | + |
| 15 | +bellman_ford_shortest_path <- function(graph, source) { |
| 16 | + # Collect all vertices (numeric indices expected) |
| 17 | + all_vertices <- unique(c(names(graph), unlist(lapply(graph, function(x) sapply(x, function(e) e$vertex))))) |
| 18 | + # Convert to numeric vector |
| 19 | + all_vertices <- as.numeric(all_vertices) |
| 20 | + num_vertices <- max(all_vertices) |
| 21 | + |
| 22 | + # Initialize distances and predecessors |
| 23 | + distances <- rep(Inf, num_vertices) |
| 24 | + predecessor <- rep(-1, num_vertices) |
| 25 | + |
| 26 | + distances[source] <- 0 |
| 27 | + |
| 28 | + # Relax edges repeatedly (V-1 times) |
| 29 | + for (i in 1:(num_vertices - 1)) { |
| 30 | + updated <- FALSE |
| 31 | + # Iterate all edges |
| 32 | + for (u_char in names(graph)) { |
| 33 | + u <- as.numeric(u_char) |
| 34 | + for (edge in graph[[u_char]]) { |
| 35 | + v <- edge$vertex |
| 36 | + w <- edge$weight |
| 37 | + if (distances[u] != Inf && distances[u] + w < distances[v]) { |
| 38 | + distances[v] <- distances[u] + w |
| 39 | + predecessor[v] <- u |
| 40 | + updated <- TRUE |
| 41 | + } |
| 42 | + } |
| 43 | + } |
| 44 | + # If no update in this pass, we can stop early |
| 45 | + if (!updated) break |
| 46 | + } |
| 47 | + |
| 48 | + # Check for negative-weight cycles: if we can still relax, there is a negative cycle |
| 49 | + negative_cycle <- FALSE |
| 50 | + for (u_char in names(graph)) { |
| 51 | + u <- as.numeric(u_char) |
| 52 | + for (edge in graph[[u_char]]) { |
| 53 | + v <- edge$vertex |
| 54 | + w <- edge$weight |
| 55 | + if (distances[u] != Inf && distances[u] + w < distances[v]) { |
| 56 | + negative_cycle <- TRUE |
| 57 | + break |
| 58 | + } |
| 59 | + } |
| 60 | + if (negative_cycle) break |
| 61 | + } |
| 62 | + |
| 63 | + return(list( |
| 64 | + distances = distances, |
| 65 | + predecessor = predecessor, |
| 66 | + negative_cycle = negative_cycle |
| 67 | + )) |
| 68 | +} |
| 69 | + |
| 70 | +# Helper to reconstruct the shortest path from source to target |
| 71 | +get_bellman_ford_path <- function(result, source, target) { |
| 72 | + if (result$negative_cycle) { |
| 73 | + return(list(path = NULL, distance = NA, message = "Negative-weight cycle detected; shortest path undefined")) |
| 74 | + } |
| 75 | + |
| 76 | + distances <- result$distances |
| 77 | + predecessor <- result$predecessor |
| 78 | + |
| 79 | + if (is.infinite(distances[target])) { |
| 80 | + return(list(path = NULL, distance = Inf, message = "Target not reachable from source")) |
| 81 | + } |
| 82 | + |
| 83 | + path <- c() |
| 84 | + current <- target |
| 85 | + while (current != -1) { |
| 86 | + path <- c(current, path) |
| 87 | + if (current == source) break |
| 88 | + current <- predecessor[current] |
| 89 | +} |
| 90 | + |
| 91 | + return(list(path = path, distance = distances[target])) |
| 92 | +} |
| 93 | + |
| 94 | +# Example usage and tests |
| 95 | +cat("=== Bellman-Ford Shortest Path Algorithm ===\n") |
| 96 | + |
| 97 | +# Example graph with negative edges but no negative cycle |
| 98 | +# Graph structure: |
| 99 | +# 1 -> 2 (6), 1 -> 3 (5), 1 -> 4 (5) |
| 100 | +# 2 -> 5 (-1) |
| 101 | +# 3 -> 2 (-2), 3 -> 5 (1) |
| 102 | +# 4 -> 3 (-2), 4 -> 6 (-1) |
| 103 | +# 5 -> 6 (3) |
| 104 | +# 6 -> (none) |
| 105 | +bf_graph <- list( |
| 106 | + "1" = list(list(vertex = 2, weight = 6), list(vertex = 3, weight = 5), list(vertex = 4, weight = 5)), |
| 107 | + "2" = list(list(vertex = 5, weight = -1)), |
| 108 | + "3" = list(list(vertex = 2, weight = -2), list(vertex = 5, weight = 1)), |
| 109 | + "4" = list(list(vertex = 3, weight = -2), list(vertex = 6, weight = -1)), |
| 110 | + "5" = list(list(vertex = 6, weight = 3)), |
| 111 | + "6" = list() |
| 112 | +) |
| 113 | + |
| 114 | +cat("Graph (adjacency list):\n") |
| 115 | +for (v in names(bf_graph)) { |
| 116 | + edges <- bf_graph[[v]] |
| 117 | + if (length(edges) > 0) { |
| 118 | + edge_strs <- sapply(edges, function(e) paste0(e$vertex, "(", e$weight, ")")) |
| 119 | + cat("Vertex", v, "-> [", paste(edge_strs, collapse = ", "), "]\n") |
| 120 | + } else { |
| 121 | + cat("Vertex", v, "-> []\n") |
| 122 | + } |
| 123 | +} |
| 124 | + |
| 125 | +cat("\nRunning Bellman-Ford from vertex 1:\n") |
| 126 | +bf_result <- bellman_ford_shortest_path(bf_graph, 1) |
| 127 | +cat("Negative cycle detected:", bf_result$negative_cycle, "\n") |
| 128 | + |
| 129 | +cat("Distances from vertex 1:\n") |
| 130 | +for (i in 1:length(bf_result$distances)) { |
| 131 | + d <- bf_result$distances[i] |
| 132 | + if (is.infinite(d)) { |
| 133 | + cat("To vertex", i, ": unreachable\n") |
| 134 | + } else { |
| 135 | + cat("To vertex", i, ": distance =", d, "\n") |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +cat("\nShortest path from 1 to 6:\n") |
| 140 | +path_info <- get_bellman_ford_path(bf_result, 1, 6) |
| 141 | +if (!is.null(path_info$path)) { |
| 142 | + cat("Path:", paste(path_info$path, collapse = " -> "), "\n") |
| 143 | + cat("Distance:", path_info$distance, "\n") |
| 144 | +} else { |
| 145 | + cat(path_info$message, "\n") |
| 146 | +} |
0 commit comments