Skip to content

Commit 69a0c30

Browse files
authored
Add Floyd–Warshall All-Pairs Shortest Path Algorithm Implementation in R (#203)
1 parent 6d15d42 commit 69a0c30

File tree

1 file changed

+285
-0
lines changed

1 file changed

+285
-0
lines changed

graph_algorithms/floyd_warshall.r

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
# Floyd-Warshall Algorithm Implementation in R
2+
# Finds shortest paths between all pairs of vertices in a weighted graph
3+
# Can handle negative edge weights, but not negative cycles
4+
# Time complexity: O(V^3) where V is number of vertices
5+
# Space complexity: O(V^2) for distance and predecessor matrices
6+
7+
8+
9+
#' FloydWarshall Class
10+
#' @description R6 class implementing the Floyd-Warshall algorithm
11+
#' @details Finds shortest paths between all pairs of vertices in a weighted directed graph.
12+
#' @importFrom R6 R6Class
13+
#' Can handle:
14+
#' - Positive and negative edge weights
15+
#' - Direct path reconstruction
16+
#' - Cycle detection
17+
#' - Disconnected components (represented by Inf)
18+
FloydWarshall <- R6::R6Class(
19+
"FloydWarshall",
20+
21+
public = list(
22+
#' @description Initialize the algorithm with graph size
23+
#' @param n_vertices Number of vertices in the graph
24+
initialize = function(n_vertices) {
25+
if (!is.numeric(n_vertices) || n_vertices < 1 || n_vertices != round(n_vertices)) {
26+
stop("Number of vertices must be a positive integer (at least 1)")
27+
}
28+
29+
self$n_vertices <- n_vertices
30+
private$initialize_matrices()
31+
invisible(self)
32+
},
33+
34+
#' @description Add a weighted edge to the graph
35+
#' @param from Source vertex (1-based indexing)
36+
#' @param to Target vertex (1-based indexing)
37+
#' @param weight Edge weight (can be negative)
38+
add_edge = function(from, to, weight) {
39+
private$validate_vertices(from, to)
40+
if (!is.numeric(weight)) {
41+
stop("Edge weight must be numeric")
42+
}
43+
44+
private$dist_matrix[from, to] <- weight
45+
private$pred_matrix[from, to] <- from
46+
invisible(self)
47+
},
48+
49+
#' @description Run the Floyd-Warshall algorithm
50+
#' @return List containing distance matrix and presence of negative cycles
51+
run = function() {
52+
# Floyd-Warshall main loop
53+
for (k in 1:self$n_vertices) {
54+
for (i in 1:self$n_vertices) {
55+
for (j in 1:self$n_vertices) {
56+
if (!is.infinite(private$dist_matrix[i, k]) &&
57+
!is.infinite(private$dist_matrix[k, j])) {
58+
new_dist <- private$dist_matrix[i, k] + private$dist_matrix[k, j]
59+
if (new_dist < private$dist_matrix[i, j]) {
60+
private$dist_matrix[i, j] <- new_dist
61+
private$pred_matrix[i, j] <- private$pred_matrix[k, j]
62+
}
63+
}
64+
}
65+
}
66+
}
67+
68+
# Check for negative cycles
69+
has_negative_cycle <- FALSE
70+
for (i in 1:self$n_vertices) {
71+
if (private$dist_matrix[i, i] < 0) {
72+
has_negative_cycle <- TRUE
73+
break
74+
}
75+
}
76+
77+
private$algorithm_run <- TRUE
78+
79+
return(list(
80+
distances = private$dist_matrix,
81+
has_negative_cycle = has_negative_cycle
82+
))
83+
},
84+
85+
#' @description Get the shortest path between two vertices
86+
#' @param from Source vertex
87+
#' @param to Target vertex
88+
#' @return List containing path and total distance
89+
get_path = function(from, to) {
90+
if (!private$algorithm_run) {
91+
stop("Run the algorithm first using run()")
92+
}
93+
94+
private$validate_vertices(from, to)
95+
96+
if (is.infinite(private$dist_matrix[from, to])) {
97+
return(list(
98+
path = numeric(0),
99+
distance = Inf,
100+
exists = FALSE
101+
))
102+
}
103+
104+
# Reconstruct path backward from 'to' using pred[from, current], then reverse
105+
path <- c()
106+
current <- to
107+
108+
while (!is.na(current) && current != from) {
109+
path <- c(current, path)
110+
prev <- private$pred_matrix[from, current]
111+
112+
# Check for cycles
113+
if (length(path) > self$n_vertices) {
114+
stop("Negative cycle detected in path reconstruction")
115+
}
116+
current <- prev
117+
}
118+
if (is.na(current)) {
119+
# No path exists
120+
return(list(
121+
path = numeric(0),
122+
distance = Inf,
123+
exists = FALSE
124+
))
125+
}
126+
path <- c(from, path)
127+
128+
return(list(
129+
path = path,
130+
distance = private$dist_matrix[from, to],
131+
exists = TRUE
132+
))
133+
},
134+
135+
#' @description Get minimum distances from a source vertex to all others
136+
#' @param from Source vertex
137+
#' @return Named vector of distances
138+
get_distances_from = function(from) {
139+
if (!private$algorithm_run) {
140+
stop("Run the algorithm first using run()")
141+
}
142+
143+
private$validate_vertices(from)
144+
d <- private$dist_matrix[from, ]
145+
names(d) <- as.character(seq_len(self$n_vertices))
146+
return(d)
147+
},
148+
149+
#' @description Check if the graph has a negative cycle
150+
#' @return TRUE if negative cycle exists, FALSE otherwise
151+
has_negative_cycle = function() {
152+
if (!private$algorithm_run) {
153+
stop("Run the algorithm first using run()")
154+
}
155+
156+
for (i in 1:self$n_vertices) {
157+
if (private$dist_matrix[i, i] < 0) {
158+
return(TRUE)
159+
}
160+
}
161+
return(FALSE)
162+
},
163+
164+
#' @description Print the distance matrix
165+
print_distances = function() {
166+
if (!private$algorithm_run) {
167+
stop("Run the algorithm first using run()")
168+
}
169+
170+
cat("Distance Matrix:\n")
171+
print(private$dist_matrix)
172+
invisible(self)
173+
},
174+
175+
# Public fields
176+
n_vertices = NULL
177+
),
178+
179+
private = list(
180+
dist_matrix = NULL,
181+
pred_matrix = NULL,
182+
algorithm_run = FALSE,
183+
184+
initialize_matrices = function() {
185+
# Initialize distance matrix with Inf for non-adjacent vertices
186+
private$dist_matrix <- matrix(Inf, nrow = self$n_vertices, ncol = self$n_vertices)
187+
diag(private$dist_matrix) <- 0
188+
189+
# Initialize predecessor matrix
190+
private$pred_matrix <- matrix(NA, nrow = self$n_vertices, ncol = self$n_vertices)
191+
for (i in 1:self$n_vertices) {
192+
private$pred_matrix[i, i] <- i
193+
}
194+
},
195+
196+
validate_vertices = function(from, to = NULL) {
197+
vertices <- if (is.null(to)) from else c(from, to)
198+
199+
if (!all(is.numeric(vertices)) ||
200+
!all(vertices == round(vertices)) ||
201+
!all(vertices >= 1) ||
202+
!all(vertices <= self$n_vertices)) {
203+
stop("Vertex indices must be integers between 1 and ", self$n_vertices)
204+
}
205+
}
206+
)
207+
)
208+
209+
# Demonstration
210+
demonstrate_floyd_warshall <- function() {
211+
cat("=== Floyd-Warshall Algorithm Demo ===\n\n")
212+
213+
# Example 1: Simple weighted graph
214+
cat("Example 1: Simple weighted graph\n")
215+
cat("Graph: 4 vertices with various weighted edges\n\n")
216+
217+
fw <- FloydWarshall$new(4)
218+
219+
# Add edges (with weights)
220+
fw$add_edge(1, 2, 5)
221+
fw$add_edge(2, 3, 3)
222+
fw$add_edge(3, 4, 1)
223+
fw$add_edge(1, 3, 10)
224+
fw$add_edge(2, 4, 6)
225+
226+
# Run algorithm
227+
result <- fw$run()
228+
229+
cat("All-pairs shortest distances:\n")
230+
fw$print_distances()
231+
232+
# Get specific path
233+
path_result <- fw$get_path(1, 4)
234+
cat("\nShortest path from 1 to 4:\n")
235+
cat(sprintf("Path: %s\n", paste(path_result$path, collapse = "")))
236+
cat(sprintf("Distance: %g\n\n", path_result$distance))
237+
238+
# Example 2: Graph with negative weights
239+
cat("Example 2: Graph with negative weights\n")
240+
cat("Graph: 3 vertices with some negative edges\n\n")
241+
242+
fw2 <- FloydWarshall$new(3)
243+
fw2$add_edge(1, 2, 4)
244+
fw2$add_edge(2, 3, -2)
245+
fw2$add_edge(1, 3, 5)
246+
247+
result2 <- fw2$run()
248+
249+
cat("All-pairs shortest distances:\n")
250+
fw2$print_distances()
251+
252+
# Example 3: Negative cycle detection
253+
cat("\nExample 3: Negative cycle detection\n")
254+
cat("Graph: 3 vertices with a negative cycle\n\n")
255+
256+
fw3 <- FloydWarshall$new(3)
257+
fw3$add_edge(1, 2, 1)
258+
fw3$add_edge(2, 3, -5)
259+
fw3$add_edge(3, 1, 2)
260+
261+
result3 <- fw3$run()
262+
263+
cat(sprintf("Contains negative cycle: %s\n\n",
264+
ifelse(result3$has_negative_cycle, "Yes", "No")))
265+
266+
# Example 4: Disconnected components
267+
cat("Example 4: Disconnected components\n")
268+
cat("Graph: 4 vertices with two components\n\n")
269+
270+
fw4 <- FloydWarshall$new(4)
271+
fw4$add_edge(1, 2, 3)
272+
fw4$add_edge(3, 4, 2)
273+
274+
result4 <- fw4$run()
275+
276+
cat("All-pairs shortest distances:\n")
277+
fw4$print_distances()
278+
279+
cat("\n=== Demo Complete ===\n")
280+
}
281+
282+
# Run demonstration only if explicitly requested via environment variable
283+
if (identical(Sys.getenv("RUN_FLOYD_WARSHALL_DEMO"), "true")) {
284+
demonstrate_floyd_warshall()
285+
}

0 commit comments

Comments
 (0)