advanced_algorithms/graph/
floyd_warshall.rs

1//! Floyd-Warshall Algorithm
2//!
3//! Computes shortest paths between all pairs of vertices in a weighted graph.
4//! Can handle negative edge weights and detect negative cycles.
5//!
6//! Time complexity: O(V³)
7//! Space complexity: O(V²)
8//!
9//! # Examples
10//!
11//! ```
12//! use advanced_algorithms::graph::{Graph, floyd_warshall};
13//!
14//! let mut graph = Graph::new(4);
15//! graph.add_edge(0, 1, 3.0);
16//! graph.add_edge(1, 2, 1.0);
17//! graph.add_edge(2, 3, 2.0);
18//! graph.add_edge(0, 3, 10.0);
19//!
20//! let result = floyd_warshall::all_pairs_shortest_path(&graph).unwrap();
21//! assert_eq!(result.distance(0, 3), 6.0);
22//! ```
23
24use crate::graph::Graph;
25use crate::{AlgorithmError, Result};
26
27/// Result of Floyd-Warshall algorithm
28#[derive(Debug, Clone)]
29pub struct AllPairsShortestPath {
30    /// Distance matrix: distance[i][j] is shortest path from i to j
31    distance: Vec<Vec<f64>>,
32    /// Next node matrix for path reconstruction
33    next: Vec<Vec<Option<usize>>>,
34    /// Number of nodes
35    _n: usize,
36}
37
38impl AllPairsShortestPath {
39    /// Gets the shortest distance from u to v
40    pub fn distance(&self, u: usize, v: usize) -> f64 {
41        self.distance[u][v]
42    }
43    
44    /// Reconstructs the shortest path from u to v
45    pub fn path(&self, u: usize, v: usize) -> Option<Vec<usize>> {
46        if self.distance[u][v].is_infinite() {
47            return None;
48        }
49        
50        let mut path = vec![u];
51        let mut current = u;
52        
53        while current != v {
54            current = self.next[current][v]?;
55            path.push(current);
56        }
57        
58        Some(path)
59    }
60    
61    /// Returns the distance matrix
62    pub fn distance_matrix(&self) -> &[Vec<f64>] {
63        &self.distance
64    }
65}
66
67/// Computes all-pairs shortest paths using Floyd-Warshall algorithm
68///
69/// # Arguments
70///
71/// * `graph` - The input graph
72///
73/// # Returns
74///
75/// AllPairsShortestPath result or error if negative cycle exists
76///
77/// # Errors
78///
79/// Returns error if the graph contains a negative cycle
80pub fn all_pairs_shortest_path(graph: &Graph) -> Result<AllPairsShortestPath> {
81    let n = graph.n_nodes;
82    
83    // Initialize distance matrix
84    let mut distance = vec![vec![f64::INFINITY; n]; n];
85    let mut next = vec![vec![None; n]; n];
86    
87    // Distance from a node to itself is 0
88    #[allow(clippy::needless_range_loop)]
89    for i in 0..n {
90        distance[i][i] = 0.0;
91    }
92    
93    // Initialize with direct edges
94    for u in 0..n {
95        for &(v, weight) in graph.neighbors(u) {
96            distance[u][v] = weight;
97            next[u][v] = Some(v);
98        }
99    }
100    
101    // Floyd-Warshall main loop
102    for k in 0..n {
103        for i in 0..n {
104            for j in 0..n {
105                let new_distance = distance[i][k] + distance[k][j];
106                
107                if new_distance < distance[i][j] {
108                    distance[i][j] = new_distance;
109                    next[i][j] = next[i][k];
110                }
111            }
112        }
113    }
114    
115    // Check for negative cycles
116    #[allow(clippy::needless_range_loop)]
117    for i in 0..n {
118        if distance[i][i] < 0.0 {
119            return Err(AlgorithmError::InvalidInput(
120                "Graph contains a negative cycle".to_string()
121            ));
122        }
123    }
124    
125    Ok(AllPairsShortestPath {
126        distance,
127        next,
128        _n: n,
129    })
130}
131
132/// Computes the transitive closure of a graph
133///
134/// Returns a matrix where result[i][j] is true if there's a path from i to j
135pub fn transitive_closure(graph: &Graph) -> Vec<Vec<bool>> {
136    let n = graph.n_nodes;
137    let mut reachable = vec![vec![false; n]; n];
138    
139    // A node can reach itself
140    #[allow(clippy::needless_range_loop)]
141    for i in 0..n {
142        reachable[i][i] = true;
143    }
144    
145    // Initialize with direct edges
146    #[allow(clippy::needless_range_loop)]
147    for u in 0..n {
148        for &(v, _) in graph.neighbors(u) {
149            reachable[u][v] = true;
150        }
151    }
152    
153    // Warshall's algorithm for transitive closure
154    for k in 0..n {
155        for i in 0..n {
156            for j in 0..n {
157                reachable[i][j] = reachable[i][j] || (reachable[i][k] && reachable[k][j]);
158            }
159        }
160    }
161    
162    reachable
163}
164
165/// Finds the graph diameter (longest shortest path)
166///
167/// # Returns
168///
169/// The diameter, or None if the graph is not connected
170pub fn diameter(graph: &Graph) -> Option<f64> {
171    match all_pairs_shortest_path(graph) {
172        Ok(apsp) => {
173            let mut max_distance: f64 = 0.0;
174            
175            for i in 0..graph.n_nodes {
176                for j in 0..graph.n_nodes {
177                    let d = apsp.distance(i, j);
178                    if d.is_finite() {
179                        max_distance = max_distance.max(d);
180                    } else if i != j {
181                        // Graph is not connected
182                        return None;
183                    }
184                }
185            }
186            
187            Some(max_distance)
188        }
189        Err(_) => None,
190    }
191}
192
193/// Finds the center of the graph (node with minimum eccentricity)
194///
195/// Eccentricity of a node is the maximum distance to any other node
196pub fn find_center(graph: &Graph) -> Option<Vec<usize>> {
197    let apsp = all_pairs_shortest_path(graph).ok()?;
198    let n = graph.n_nodes;
199    
200    let mut eccentricities = Vec::with_capacity(n);
201    
202    for i in 0..n {
203        let mut max_distance: f64 = 0.0;
204        
205        for j in 0..n {
206            let d = apsp.distance(i, j);
207            if d.is_finite() {
208                max_distance = max_distance.max(d);
209            } else {
210                // Graph is not connected
211                return None;
212            }
213        }
214        
215        eccentricities.push(max_distance);
216    }
217    
218    let min_eccentricity = eccentricities.iter()
219        .fold(f64::INFINITY, |a, &b| a.min(b));
220    
221    let centers: Vec<usize> = eccentricities.iter()
222        .enumerate()
223        .filter(|(_, &e)| (e - min_eccentricity).abs() < 1e-10)
224        .map(|(i, _)| i)
225        .collect();
226    
227    Some(centers)
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233    
234    #[test]
235    fn test_simple_graph() {
236        let mut graph = Graph::new(4);
237        graph.add_edge(0, 1, 1.0);
238        graph.add_edge(1, 2, 2.0);
239        graph.add_edge(2, 3, 3.0);
240        graph.add_edge(0, 3, 10.0);
241        
242        let result = all_pairs_shortest_path(&graph).unwrap();
243        
244        assert_eq!(result.distance(0, 3), 6.0);
245        assert_eq!(result.distance(0, 2), 3.0);
246        
247        let path = result.path(0, 3).unwrap();
248        assert_eq!(path, vec![0, 1, 2, 3]);
249    }
250    
251    #[test]
252    fn test_all_pairs() {
253        let mut graph = Graph::new(3);
254        graph.add_edge(0, 1, 1.0);
255        graph.add_edge(1, 2, 2.0);
256        graph.add_edge(0, 2, 5.0);
257        
258        let result = all_pairs_shortest_path(&graph).unwrap();
259        
260        assert_eq!(result.distance(0, 1), 1.0);
261        assert_eq!(result.distance(0, 2), 3.0);
262        assert_eq!(result.distance(1, 2), 2.0);
263    }
264    
265    #[test]
266    fn test_negative_cycle() {
267        let mut graph = Graph::new(3);
268        graph.add_edge(0, 1, 1.0);
269        graph.add_edge(1, 2, -3.0);
270        graph.add_edge(2, 0, 1.0);
271        
272        let result = all_pairs_shortest_path(&graph);
273        assert!(result.is_err());
274    }
275    
276    #[test]
277    fn test_transitive_closure() {
278        let mut graph = Graph::new(4);
279        graph.add_edge(0, 1, 1.0);
280        graph.add_edge(1, 2, 1.0);
281        graph.add_edge(2, 3, 1.0);
282        
283        let closure = transitive_closure(&graph);
284        
285        assert!(closure[0][3]); // 0 can reach 3
286        assert!(!closure[3][0]); // 3 cannot reach 0
287    }
288    
289    #[test]
290    fn test_diameter() {
291        let mut graph = Graph::new(4);
292        graph.add_edge(0, 1, 1.0);
293        graph.add_edge(1, 2, 1.0);
294        graph.add_edge(2, 3, 1.0);
295        graph.add_undirected_edge(0, 3, 1.0);
296        
297        let d = diameter(&graph);
298        assert!(d.is_some());
299        // Diameter is 3.0: longest path is 1→2→3→0 or 3→0→1→2
300        assert_eq!(d.unwrap(), 3.0);
301    }
302}