advanced_algorithms/graph/
bellman_ford.rs

1//! Bellman-Ford Algorithm
2//!
3//! Computes shortest paths from a source vertex to all other vertices in a weighted
4//! graph. Unlike Dijkstra's algorithm, it can handle graphs with negative edge weights
5//! and can detect negative cycles.
6//!
7//! Time complexity: O(VE)
8//!
9//! # Examples
10//!
11//! ```
12//! use advanced_algorithms::graph::{Graph, bellman_ford};
13//!
14//! let mut graph = Graph::new(4);
15//! graph.add_edge(0, 1, 1.0);
16//! graph.add_edge(1, 2, -1.0);  // Negative weight OK
17//! graph.add_edge(2, 3, 1.0);
18//!
19//! let result = bellman_ford::shortest_path(&graph, 0).unwrap();
20//! assert_eq!(result.distance[3], 1.0);
21//! ```
22
23use crate::graph::Graph;
24use crate::{AlgorithmError, Result};
25
26/// Result of Bellman-Ford algorithm
27#[derive(Debug, Clone)]
28pub struct ShortestPathResult {
29    /// Distance from source to each node
30    pub distance: Vec<f64>,
31    /// Previous node in shortest path
32    pub previous: Vec<Option<usize>>,
33    /// Source node
34    pub source: usize,
35}
36
37impl ShortestPathResult {
38    /// Reconstructs the path from source to target
39    pub fn path_to(&self, target: usize) -> Option<Vec<usize>> {
40        if self.distance[target].is_infinite() {
41            return None;
42        }
43        
44        let mut path = Vec::new();
45        let mut current = target;
46        
47        while current != self.source {
48            path.push(current);
49            current = self.previous[current]?;
50        }
51        
52        path.push(self.source);
53        path.reverse();
54        
55        Some(path)
56    }
57}
58
59/// Computes shortest paths using Bellman-Ford algorithm
60///
61/// # Arguments
62///
63/// * `graph` - The input graph (can have negative weights)
64/// * `source` - Starting node
65///
66/// # Returns
67///
68/// ShortestPathResult or error if negative cycle detected
69///
70/// # Errors
71///
72/// Returns error if the graph contains a negative cycle reachable from source
73pub fn shortest_path(graph: &Graph, source: usize) -> Result<ShortestPathResult> {
74    let n = graph.n_nodes;
75    
76    let mut distance = vec![f64::INFINITY; n];
77    let mut previous = vec![None; n];
78    
79    distance[source] = 0.0;
80    
81    // Relax all edges V-1 times
82    for _ in 0..n - 1 {
83        let mut changed = false;
84        
85        for u in 0..n {
86            if distance[u].is_infinite() {
87                continue;
88            }
89            
90            for &(v, weight) in graph.neighbors(u) {
91                let new_distance = distance[u] + weight;
92                
93                if new_distance < distance[v] {
94                    distance[v] = new_distance;
95                    previous[v] = Some(u);
96                    changed = true;
97                }
98            }
99        }
100        
101        // Early termination if no changes
102        if !changed {
103            break;
104        }
105    }
106    
107    // Check for negative cycles
108    for u in 0..n {
109        if distance[u].is_infinite() {
110            continue;
111        }
112        
113        for &(v, weight) in graph.neighbors(u) {
114            if distance[u] + weight < distance[v] {
115                return Err(AlgorithmError::InvalidInput(
116                    "Graph contains a negative cycle".to_string()
117                ));
118            }
119        }
120    }
121    
122    Ok(ShortestPathResult {
123        distance,
124        previous,
125        source,
126    })
127}
128
129/// Detects if the graph contains any negative cycle
130///
131/// # Arguments
132///
133/// * `graph` - The input graph
134///
135/// # Returns
136///
137/// `true` if a negative cycle exists, `false` otherwise
138pub fn has_negative_cycle(graph: &Graph) -> bool {
139    let n = graph.n_nodes;
140    let mut distance = vec![0.0; n];
141    
142    // Relax all edges V-1 times
143    for _ in 0..n - 1 {
144        for u in 0..n {
145            for &(v, weight) in graph.neighbors(u) {
146                let new_distance = distance[u] + weight;
147                if new_distance < distance[v] {
148                    distance[v] = new_distance;
149                }
150            }
151        }
152    }
153    
154    // Check for negative cycle
155    for u in 0..n {
156        for &(v, weight) in graph.neighbors(u) {
157            if distance[u] + weight < distance[v] {
158                return true;
159            }
160        }
161    }
162    
163    false
164}
165
166/// Finds a negative cycle in the graph if one exists
167///
168/// # Returns
169///
170/// Option containing the nodes in the negative cycle
171pub fn find_negative_cycle(graph: &Graph) -> Option<Vec<usize>> {
172    let n = graph.n_nodes;
173    let mut distance = vec![0.0; n];
174    let mut previous = vec![None; n];
175    
176    // Relax all edges V times to detect and mark negative cycles
177    let mut last_updated = None;
178    
179    for _iteration in 0..n {
180        let mut changed = false;
181        
182        for u in 0..n {
183            for &(v, weight) in graph.neighbors(u) {
184                let new_distance = distance[u] + weight;
185                
186                if new_distance < distance[v] {
187                    distance[v] = new_distance;
188                    previous[v] = Some(u);
189                    changed = true;
190                    last_updated = Some(v);
191                }
192            }
193        }
194        
195        if !changed {
196            return None;
197        }
198    }
199    
200    // If we had updates in the V-th iteration, there's a negative cycle
201    if let Some(mut node) = last_updated {
202        // Trace back to find a node definitely in the cycle
203        for _ in 0..n {
204            node = previous[node]?;
205        }
206        
207        // Reconstruct the cycle
208        let mut cycle = vec![node];
209        let mut current = previous[node]?;
210        
211        while current != node {
212            cycle.push(current);
213            current = previous[current]?;
214        }
215        
216        cycle.reverse();
217        return Some(cycle);
218    }
219    
220    None
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226    
227    #[test]
228    fn test_positive_weights() {
229        let mut graph = Graph::new(4);
230        graph.add_edge(0, 1, 1.0);
231        graph.add_edge(1, 2, 2.0);
232        graph.add_edge(2, 3, 3.0);
233        
234        let result = shortest_path(&graph, 0).unwrap();
235        
236        assert_eq!(result.distance[3], 6.0);
237    }
238    
239    #[test]
240    fn test_negative_weights() {
241        let mut graph = Graph::new(4);
242        graph.add_edge(0, 1, 1.0);
243        graph.add_edge(1, 2, -3.0);
244        graph.add_edge(2, 3, 1.0);
245        graph.add_edge(0, 3, 5.0);
246        
247        let result = shortest_path(&graph, 0).unwrap();
248        
249        assert_eq!(result.distance[3], -1.0); // 0->1->2->3
250    }
251    
252    #[test]
253    fn test_negative_cycle() {
254        let mut graph = Graph::new(3);
255        graph.add_edge(0, 1, 1.0);
256        graph.add_edge(1, 2, -3.0);
257        graph.add_edge(2, 0, 1.0); // Creates negative cycle
258        
259        let result = shortest_path(&graph, 0);
260        assert!(result.is_err());
261        
262        assert!(has_negative_cycle(&graph));
263        
264        let cycle = find_negative_cycle(&graph);
265        assert!(cycle.is_some());
266    }
267    
268    #[test]
269    fn test_disconnected() {
270        let mut graph = Graph::new(3);
271        graph.add_edge(0, 1, 1.0);
272        
273        let result = shortest_path(&graph, 0).unwrap();
274        
275        assert!(result.distance[2].is_infinite());
276    }
277}