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(" \n Shortest 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(" \n Example 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