scirs2_graph/algorithms/
connectivity.rs

1//! Graph connectivity algorithms
2//!
3//! This module contains algorithms for analyzing graph connectivity properties
4//! including connected components, articulation points, bridges, and bipartite checking.
5
6use crate::base::{DiGraph, EdgeWeight, Graph, Node};
7use petgraph::Direction;
8use std::collections::{HashMap, HashSet, VecDeque};
9
10/// Each connected component is represented as a set of nodes
11pub type Component<N> = HashSet<N>;
12
13/// Finds all connected components in an undirected graph
14///
15/// # Arguments
16/// * `graph` - The graph to analyze
17///
18/// # Returns
19/// * A vector of connected components, where each component is a set of nodes
20#[allow(dead_code)]
21pub fn connected_components<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Vec<Component<N>>
22where
23    N: Node + std::fmt::Debug,
24    E: EdgeWeight,
25    Ix: petgraph::graph::IndexType,
26{
27    let mut components: Vec<Component<N>> = Vec::new();
28    let mut visited = HashSet::new();
29
30    // For each node in the graph
31    for node_idx in graph.inner().node_indices() {
32        // Skip if already visited
33        if visited.contains(&node_idx) {
34            continue;
35        }
36
37        // BFS to find all nodes in this component
38        let mut component = HashSet::new();
39        let mut queue = VecDeque::new();
40        queue.push_back(node_idx);
41        visited.insert(node_idx);
42
43        while let Some(current) = queue.pop_front() {
44            component.insert(graph.inner()[current].clone());
45
46            // Visit all unvisited neighbors
47            for neighbor in graph.inner().neighbors(current) {
48                if !visited.contains(&neighbor) {
49                    visited.insert(neighbor);
50                    queue.push_back(neighbor);
51                }
52            }
53        }
54
55        components.push(component);
56    }
57
58    components
59}
60
61/// Finds all strongly connected components in a directed graph using Tarjan's algorithm
62///
63/// # Arguments
64/// * `graph` - The directed graph to analyze
65///
66/// # Returns
67/// * A vector of strongly connected components
68#[allow(dead_code)]
69pub fn strongly_connected_components<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Vec<Component<N>>
70where
71    N: Node + std::fmt::Debug,
72    E: EdgeWeight,
73    Ix: petgraph::graph::IndexType,
74{
75    struct TarjanState<Ix: petgraph::graph::IndexType> {
76        index: usize,
77        stack: Vec<petgraph::graph::NodeIndex<Ix>>,
78        indices: HashMap<petgraph::graph::NodeIndex<Ix>, usize>,
79        lowlinks: HashMap<petgraph::graph::NodeIndex<Ix>, usize>,
80        on_stack: HashSet<petgraph::graph::NodeIndex<Ix>>,
81    }
82
83    fn strongconnect<N, E, Ix>(
84        v: petgraph::graph::NodeIndex<Ix>,
85        graph: &DiGraph<N, E, Ix>,
86        state: &mut TarjanState<Ix>,
87        components: &mut Vec<Component<N>>,
88    ) where
89        N: Node + std::fmt::Debug,
90        E: EdgeWeight,
91        Ix: petgraph::graph::IndexType,
92    {
93        state.indices.insert(v, state.index);
94        state.lowlinks.insert(v, state.index);
95        state.index += 1;
96        state.stack.push(v);
97        state.on_stack.insert(v);
98
99        // Consider successors of v
100        for w in graph.inner().neighbors_directed(v, Direction::Outgoing) {
101            if !state.indices.contains_key(&w) {
102                // Successor w has not yet been visited; recurse on it
103                strongconnect(w, graph, state, components);
104                let w_lowlink = *state.lowlinks.get(&w).expect("Operation failed");
105                let v_lowlink = *state.lowlinks.get(&v).expect("Operation failed");
106                state.lowlinks.insert(v, v_lowlink.min(w_lowlink));
107            } else if state.on_stack.contains(&w) {
108                // Successor w is in stack S and hence in the current SCC
109                let w_index = *state.indices.get(&w).expect("Operation failed");
110                let v_lowlink = *state.lowlinks.get(&v).expect("Operation failed");
111                state.lowlinks.insert(v, v_lowlink.min(w_index));
112            }
113        }
114
115        // If v is a root node, pop the stack and create an SCC
116        if state.lowlinks.get(&v) == state.indices.get(&v) {
117            let mut component = HashSet::new();
118            loop {
119                let w = state.stack.pop().expect("Operation failed");
120                state.on_stack.remove(&w);
121                component.insert(graph.inner()[w].clone());
122                if w == v {
123                    break;
124                }
125            }
126            if !component.is_empty() {
127                components.push(component);
128            }
129        }
130    }
131
132    let mut state = TarjanState {
133        index: 0,
134        stack: Vec::new(),
135        indices: HashMap::new(),
136        lowlinks: HashMap::new(),
137        on_stack: HashSet::new(),
138    };
139    let mut components = Vec::new();
140
141    for v in graph.inner().node_indices() {
142        if !state.indices.contains_key(&v) {
143            strongconnect(v, graph, &mut state, &mut components);
144        }
145    }
146
147    components
148}
149
150/// Finds all weakly connected components in a directed graph
151///
152/// Weakly connected components are found by treating the directed graph as undirected
153/// and finding connected components. Two vertices are in the same weakly connected
154/// component if there is an undirected path between them.
155///
156/// # Arguments
157/// * `graph` - The directed graph to analyze
158///
159/// # Returns
160/// * A vector of weakly connected components
161#[allow(dead_code)]
162pub fn weakly_connected_components<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Vec<Component<N>>
163where
164    N: Node + std::fmt::Debug,
165    E: EdgeWeight,
166    Ix: petgraph::graph::IndexType,
167{
168    let mut components: Vec<Component<N>> = Vec::new();
169    let mut visited = HashSet::new();
170
171    // For each node in the graph
172    for node_idx in graph.inner().node_indices() {
173        // Skip if already visited
174        if visited.contains(&node_idx) {
175            continue;
176        }
177
178        // BFS to find all nodes in this weakly connected component
179        let mut component = HashSet::new();
180        let mut queue = VecDeque::new();
181        queue.push_back(node_idx);
182        visited.insert(node_idx);
183
184        while let Some(current) = queue.pop_front() {
185            component.insert(graph.inner()[current].clone());
186
187            // Visit all unvisited neighbors (both incoming and outgoing)
188            for neighbor in graph.inner().neighbors_undirected(current) {
189                if !visited.contains(&neighbor) {
190                    visited.insert(neighbor);
191                    queue.push_back(neighbor);
192                }
193            }
194        }
195
196        components.push(component);
197    }
198
199    components
200}
201
202/// Finds all articulation points (cut vertices) in an undirected graph
203///
204/// An articulation point is a vertex whose removal increases the number of connected components.
205///
206/// # Arguments
207/// * `graph` - The undirected graph to analyze
208///
209/// # Returns
210/// * A set of articulation points
211#[allow(dead_code)]
212pub fn articulation_points<N, E, Ix>(graph: &Graph<N, E, Ix>) -> HashSet<N>
213where
214    N: Node + std::fmt::Debug,
215    E: EdgeWeight,
216    Ix: petgraph::graph::IndexType,
217{
218    struct DfsState<Ix: petgraph::graph::IndexType> {
219        time: usize,
220        disc: HashMap<petgraph::graph::NodeIndex<Ix>, usize>,
221        low: HashMap<petgraph::graph::NodeIndex<Ix>, usize>,
222        parent: HashMap<petgraph::graph::NodeIndex<Ix>, Option<petgraph::graph::NodeIndex<Ix>>>,
223        visited: HashSet<petgraph::graph::NodeIndex<Ix>>,
224    }
225
226    fn dfs<N, E, Ix>(
227        u: petgraph::graph::NodeIndex<Ix>,
228        graph: &Graph<N, E, Ix>,
229        state: &mut DfsState<Ix>,
230        articulation_points: &mut HashSet<N>,
231    ) where
232        N: Node + std::fmt::Debug,
233        E: EdgeWeight,
234        Ix: petgraph::graph::IndexType,
235    {
236        state.visited.insert(u);
237        state.disc.insert(u, state.time);
238        state.low.insert(u, state.time);
239        state.time += 1;
240
241        let mut children = 0;
242
243        for v in graph.inner().neighbors(u) {
244            if !state.visited.contains(&v) {
245                children += 1;
246                state.parent.insert(v, Some(u));
247                dfs(v, graph, state, articulation_points);
248
249                // Check if the subtree rooted at v has a back edge to an ancestor of u
250                let v_low = *state.low.get(&v).expect("Operation failed");
251                let u_low = *state.low.get(&u).expect("Operation failed");
252                state.low.insert(u, u_low.min(v_low));
253
254                // u is an articulation point if:
255                // (1) u is root and has more than one child
256                // (2) u is not root and low[v] >= disc[u]
257                let u_disc = *state.disc.get(&u).expect("Operation failed");
258                if (state.parent.get(&u).expect("Operation failed").is_none() && children > 1)
259                    || (state.parent.get(&u).expect("Operation failed").is_some()
260                        && v_low >= u_disc)
261                {
262                    articulation_points.insert(graph.inner()[u].clone());
263                }
264            } else if state.parent.get(&u).expect("Operation failed") != &Some(v) {
265                // Update low[u] for back edge
266                let v_disc = *state.disc.get(&v).expect("Operation failed");
267                let u_low = *state.low.get(&u).expect("Operation failed");
268                state.low.insert(u, u_low.min(v_disc));
269            }
270        }
271    }
272
273    let mut articulation_points = HashSet::new();
274    let mut state = DfsState {
275        time: 0,
276        disc: HashMap::new(),
277        low: HashMap::new(),
278        parent: HashMap::new(),
279        visited: HashSet::new(),
280    };
281
282    for node in graph.inner().node_indices() {
283        if !state.visited.contains(&node) {
284            state.parent.insert(node, None);
285            dfs(node, graph, &mut state, &mut articulation_points);
286        }
287    }
288
289    articulation_points
290}
291
292/// Result of bipartite checking
293#[derive(Debug, Clone)]
294pub struct BipartiteResult<N: Node> {
295    /// Whether the graph is bipartite
296    pub is_bipartite: bool,
297    /// Node coloring (0 or 1) if bipartite, empty if not
298    pub coloring: HashMap<N, u8>,
299}
300
301/// Checks if a graph is bipartite and returns the coloring if it is
302///
303/// A graph is bipartite if its vertices can be divided into two disjoint sets
304/// such that no two vertices within the same set are adjacent.
305///
306/// # Arguments
307/// * `graph` - The graph to check
308///
309/// # Returns
310/// * A BipartiteResult indicating if the graph is bipartite and the coloring
311#[allow(dead_code)]
312pub fn is_bipartite<N, E, Ix>(graph: &Graph<N, E, Ix>) -> BipartiteResult<N>
313where
314    N: Node + std::fmt::Debug,
315    E: EdgeWeight,
316    Ix: petgraph::graph::IndexType,
317{
318    let mut coloring: HashMap<petgraph::graph::NodeIndex<Ix>, u8> = HashMap::new();
319    let mut queue = VecDeque::new();
320
321    // Check each connected component
322    for start_idx in graph.inner().node_indices() {
323        if coloring.contains_key(&start_idx) {
324            continue;
325        }
326
327        // Start BFS coloring from this node
328        queue.push_back(start_idx);
329        coloring.insert(start_idx, 0);
330
331        while let Some(node_idx) = queue.pop_front() {
332            let node_color = *coloring.get(&node_idx).expect("Operation failed");
333            let next_color = 1 - node_color;
334
335            for neighbor in graph.inner().neighbors(node_idx) {
336                if let Some(&neighbor_color) = coloring.get(&neighbor) {
337                    // If neighbor has same color, not bipartite
338                    if neighbor_color == node_color {
339                        return BipartiteResult {
340                            is_bipartite: false,
341                            coloring: HashMap::new(),
342                        };
343                    }
344                } else {
345                    // Color the neighbor and add to queue
346                    coloring.insert(neighbor, next_color);
347                    queue.push_back(neighbor);
348                }
349            }
350        }
351    }
352
353    // Convert node indices to actual nodes
354    let node_coloring: HashMap<N, u8> = coloring
355        .into_iter()
356        .map(|(idx, color)| (graph.inner()[idx].clone(), color))
357        .collect();
358
359    BipartiteResult {
360        is_bipartite: true,
361        coloring: node_coloring,
362    }
363}
364
365/// Finds all bridges (cut edges) in an undirected graph
366///
367/// A bridge is an edge whose removal increases the number of connected components.
368///
369/// # Arguments
370/// * `graph` - The undirected graph to analyze
371///
372/// # Returns
373/// * A vector of bridges, where each bridge is represented as a tuple of nodes
374#[allow(dead_code)]
375pub fn bridges<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Vec<(N, N)>
376where
377    N: Node + std::fmt::Debug,
378    E: EdgeWeight,
379    Ix: petgraph::graph::IndexType,
380{
381    struct DfsState<Ix: petgraph::graph::IndexType> {
382        time: usize,
383        disc: HashMap<petgraph::graph::NodeIndex<Ix>, usize>,
384        low: HashMap<petgraph::graph::NodeIndex<Ix>, usize>,
385        parent: HashMap<petgraph::graph::NodeIndex<Ix>, Option<petgraph::graph::NodeIndex<Ix>>>,
386        visited: HashSet<petgraph::graph::NodeIndex<Ix>>,
387    }
388
389    fn dfs<N, E, Ix>(
390        u: petgraph::graph::NodeIndex<Ix>,
391        graph: &Graph<N, E, Ix>,
392        state: &mut DfsState<Ix>,
393        bridges: &mut Vec<(N, N)>,
394    ) where
395        N: Node + std::fmt::Debug,
396        E: EdgeWeight,
397        Ix: petgraph::graph::IndexType,
398    {
399        state.visited.insert(u);
400        state.disc.insert(u, state.time);
401        state.low.insert(u, state.time);
402        state.time += 1;
403
404        for v in graph.inner().neighbors(u) {
405            if !state.visited.contains(&v) {
406                state.parent.insert(v, Some(u));
407                dfs(v, graph, state, bridges);
408
409                // Check if the subtree rooted at v has a back edge to an ancestor of u
410                let v_low = *state.low.get(&v).expect("Operation failed");
411                let u_low = *state.low.get(&u).expect("Operation failed");
412                state.low.insert(u, u_low.min(v_low));
413
414                // If low[v] > disc[u], then (u, v) is a bridge
415                let u_disc = *state.disc.get(&u).expect("Operation failed");
416                if v_low > u_disc {
417                    bridges.push((graph.inner()[u].clone(), graph.inner()[v].clone()));
418                }
419            } else if state.parent.get(&u).expect("Operation failed") != &Some(v) {
420                // Update low[u] for back edge
421                let v_disc = *state.disc.get(&v).expect("Operation failed");
422                let u_low = *state.low.get(&u).expect("Operation failed");
423                state.low.insert(u, u_low.min(v_disc));
424            }
425        }
426    }
427
428    let mut bridges = Vec::new();
429    let mut state = DfsState {
430        time: 0,
431        disc: HashMap::new(),
432        low: HashMap::new(),
433        parent: HashMap::new(),
434        visited: HashSet::new(),
435    };
436
437    for node in graph.inner().node_indices() {
438        if !state.visited.contains(&node) {
439            state.parent.insert(node, None);
440            dfs(node, graph, &mut state, &mut bridges);
441        }
442    }
443
444    bridges
445}
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450    use crate::error::Result as GraphResult;
451    use crate::generators::create_graph;
452
453    #[test]
454    fn test_connected_components() -> GraphResult<()> {
455        // Create a graph with two components: {0, 1, 2} and {3, 4}
456        let mut graph = create_graph::<i32, ()>();
457
458        graph.add_edge(0, 1, ())?;
459        graph.add_edge(1, 2, ())?;
460        graph.add_edge(3, 4, ())?;
461
462        let components = connected_components(&graph);
463        assert_eq!(components.len(), 2);
464
465        // Check component sizes
466        let sizes: Vec<usize> = components.iter().map(|c| c.len()).collect();
467        assert!(sizes.contains(&3));
468        assert!(sizes.contains(&2));
469
470        Ok(())
471    }
472
473    #[test]
474    fn test_strongly_connected_components() -> GraphResult<()> {
475        // Create a directed graph with SCCs
476        let mut graph = crate::base::DiGraph::<&str, ()>::new();
477
478        // Create a cycle A -> B -> C -> A
479        graph.add_edge("A", "B", ())?;
480        graph.add_edge("B", "C", ())?;
481        graph.add_edge("C", "A", ())?;
482
483        // Add isolated node D by adding an edge from D to E
484        graph.add_edge("D", "E", ())?;
485
486        let sccs = strongly_connected_components(&graph);
487        assert_eq!(sccs.len(), 3);
488
489        // One SCC should have 3 nodes (A, B, C), two should have 1 (D and E)
490        let sizes: Vec<usize> = sccs.iter().map(|c| c.len()).collect();
491        assert!(sizes.contains(&3));
492        assert_eq!(sizes.iter().filter(|&&s| s == 1).count(), 2);
493
494        Ok(())
495    }
496
497    #[test]
498    fn test_articulation_points() -> GraphResult<()> {
499        // Create a graph where node 1 is an articulation point
500        let mut graph = create_graph::<i32, ()>();
501
502        // Structure: 0 - 1 - 2
503        //                |
504        //                3
505        graph.add_edge(0, 1, ())?;
506        graph.add_edge(1, 2, ())?;
507        graph.add_edge(1, 3, ())?;
508
509        let aps = articulation_points(&graph);
510        assert_eq!(aps.len(), 1);
511        assert!(aps.contains(&1));
512
513        Ok(())
514    }
515
516    #[test]
517    fn test_is_bipartite() -> GraphResult<()> {
518        // Create a bipartite graph (square)
519        let mut bipartite = create_graph::<i32, ()>();
520
521        bipartite.add_edge(0, 1, ())?;
522        bipartite.add_edge(1, 2, ())?;
523        bipartite.add_edge(2, 3, ())?;
524        bipartite.add_edge(3, 0, ())?;
525
526        let result = is_bipartite(&bipartite);
527        assert!(result.is_bipartite);
528        assert_eq!(result.coloring.len(), 4);
529
530        // Create a non-bipartite graph (triangle)
531        let mut non_bipartite = create_graph::<i32, ()>();
532
533        non_bipartite.add_edge(0, 1, ())?;
534        non_bipartite.add_edge(1, 2, ())?;
535        non_bipartite.add_edge(2, 0, ())?;
536
537        let result = is_bipartite(&non_bipartite);
538        assert!(!result.is_bipartite);
539
540        Ok(())
541    }
542
543    #[test]
544    fn test_weakly_connected_components() -> GraphResult<()> {
545        // Create a directed graph with two weakly connected components
546        let mut graph = crate::base::DiGraph::<&str, ()>::new();
547
548        // Component 1: A -> B -> C (weakly connected but not strongly connected)
549        graph.add_edge("A", "B", ())?;
550        graph.add_edge("B", "C", ())?;
551
552        // Component 2: D -> E <- F (triangle, weakly connected)
553        graph.add_edge("D", "E", ())?;
554        graph.add_edge("F", "E", ())?;
555        graph.add_edge("D", "F", ())?;
556
557        let wccs = weakly_connected_components(&graph);
558        assert_eq!(wccs.len(), 2);
559
560        // One component should have 3 nodes, one should have 3 nodes
561        let sizes: Vec<usize> = wccs.iter().map(|c| c.len()).collect();
562        assert!(sizes.contains(&3));
563        assert_eq!(sizes.iter().filter(|&&s| s == 3).count(), 2);
564
565        Ok(())
566    }
567
568    #[test]
569    fn test_bridges() -> GraphResult<()> {
570        // Create a graph with a bridge
571        let mut graph = create_graph::<i32, ()>();
572
573        // Create two triangles connected by a bridge
574        // Triangle 1: 0-1-2
575        graph.add_edge(0, 1, ())?;
576        graph.add_edge(1, 2, ())?;
577        graph.add_edge(2, 0, ())?;
578
579        // Bridge: 2-3
580        graph.add_edge(2, 3, ())?;
581
582        // Add another triangle to make bridge more obvious
583        graph.add_edge(3, 4, ())?;
584        graph.add_edge(4, 5, ())?;
585        graph.add_edge(5, 3, ())?;
586
587        let bridges_found = bridges(&graph);
588        assert_eq!(bridges_found.len(), 1);
589
590        // The bridge should be (2, 3) or (3, 2)
591        let bridge = &bridges_found[0];
592        assert!((bridge.0 == 2 && bridge.1 == 3) || (bridge.0 == 3 && bridge.1 == 2));
593
594        Ok(())
595    }
596}