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).unwrap();
105                let v_lowlink = *state.lowlinks.get(&v).unwrap();
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).unwrap();
110                let v_lowlink = *state.lowlinks.get(&v).unwrap();
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().unwrap();
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).unwrap();
251                let u_low = *state.low.get(&u).unwrap();
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).unwrap();
258                if (state.parent.get(&u).unwrap().is_none() && children > 1)
259                    || (state.parent.get(&u).unwrap().is_some() && v_low >= u_disc)
260                {
261                    articulation_points.insert(graph.inner()[u].clone());
262                }
263            } else if state.parent.get(&u).unwrap() != &Some(v) {
264                // Update low[u] for back edge
265                let v_disc = *state.disc.get(&v).unwrap();
266                let u_low = *state.low.get(&u).unwrap();
267                state.low.insert(u, u_low.min(v_disc));
268            }
269        }
270    }
271
272    let mut articulation_points = HashSet::new();
273    let mut state = DfsState {
274        time: 0,
275        disc: HashMap::new(),
276        low: HashMap::new(),
277        parent: HashMap::new(),
278        visited: HashSet::new(),
279    };
280
281    for node in graph.inner().node_indices() {
282        if !state.visited.contains(&node) {
283            state.parent.insert(node, None);
284            dfs(node, graph, &mut state, &mut articulation_points);
285        }
286    }
287
288    articulation_points
289}
290
291/// Result of bipartite checking
292#[derive(Debug, Clone)]
293pub struct BipartiteResult<N: Node> {
294    /// Whether the graph is bipartite
295    pub is_bipartite: bool,
296    /// Node coloring (0 or 1) if bipartite, empty if not
297    pub coloring: HashMap<N, u8>,
298}
299
300/// Checks if a graph is bipartite and returns the coloring if it is
301///
302/// A graph is bipartite if its vertices can be divided into two disjoint sets
303/// such that no two vertices within the same set are adjacent.
304///
305/// # Arguments
306/// * `graph` - The graph to check
307///
308/// # Returns
309/// * A BipartiteResult indicating if the graph is bipartite and the coloring
310#[allow(dead_code)]
311pub fn is_bipartite<N, E, Ix>(graph: &Graph<N, E, Ix>) -> BipartiteResult<N>
312where
313    N: Node + std::fmt::Debug,
314    E: EdgeWeight,
315    Ix: petgraph::graph::IndexType,
316{
317    let mut coloring: HashMap<petgraph::graph::NodeIndex<Ix>, u8> = HashMap::new();
318    let mut queue = VecDeque::new();
319
320    // Check each connected component
321    for start_idx in graph.inner().node_indices() {
322        if coloring.contains_key(&start_idx) {
323            continue;
324        }
325
326        // Start BFS coloring from this node
327        queue.push_back(start_idx);
328        coloring.insert(start_idx, 0);
329
330        while let Some(node_idx) = queue.pop_front() {
331            let node_color = *coloring.get(&node_idx).unwrap();
332            let next_color = 1 - node_color;
333
334            for neighbor in graph.inner().neighbors(node_idx) {
335                if let Some(&neighbor_color) = coloring.get(&neighbor) {
336                    // If neighbor has same color, not bipartite
337                    if neighbor_color == node_color {
338                        return BipartiteResult {
339                            is_bipartite: false,
340                            coloring: HashMap::new(),
341                        };
342                    }
343                } else {
344                    // Color the neighbor and add to queue
345                    coloring.insert(neighbor, next_color);
346                    queue.push_back(neighbor);
347                }
348            }
349        }
350    }
351
352    // Convert node indices to actual nodes
353    let node_coloring: HashMap<N, u8> = coloring
354        .into_iter()
355        .map(|(idx, color)| (graph.inner()[idx].clone(), color))
356        .collect();
357
358    BipartiteResult {
359        is_bipartite: true,
360        coloring: node_coloring,
361    }
362}
363
364/// Finds all bridges (cut edges) in an undirected graph
365///
366/// A bridge is an edge whose removal increases the number of connected components.
367///
368/// # Arguments
369/// * `graph` - The undirected graph to analyze
370///
371/// # Returns
372/// * A vector of bridges, where each bridge is represented as a tuple of nodes
373#[allow(dead_code)]
374pub fn bridges<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Vec<(N, N)>
375where
376    N: Node + std::fmt::Debug,
377    E: EdgeWeight,
378    Ix: petgraph::graph::IndexType,
379{
380    struct DfsState<Ix: petgraph::graph::IndexType> {
381        time: usize,
382        disc: HashMap<petgraph::graph::NodeIndex<Ix>, usize>,
383        low: HashMap<petgraph::graph::NodeIndex<Ix>, usize>,
384        parent: HashMap<petgraph::graph::NodeIndex<Ix>, Option<petgraph::graph::NodeIndex<Ix>>>,
385        visited: HashSet<petgraph::graph::NodeIndex<Ix>>,
386    }
387
388    fn dfs<N, E, Ix>(
389        u: petgraph::graph::NodeIndex<Ix>,
390        graph: &Graph<N, E, Ix>,
391        state: &mut DfsState<Ix>,
392        bridges: &mut Vec<(N, N)>,
393    ) where
394        N: Node + std::fmt::Debug,
395        E: EdgeWeight,
396        Ix: petgraph::graph::IndexType,
397    {
398        state.visited.insert(u);
399        state.disc.insert(u, state.time);
400        state.low.insert(u, state.time);
401        state.time += 1;
402
403        for v in graph.inner().neighbors(u) {
404            if !state.visited.contains(&v) {
405                state.parent.insert(v, Some(u));
406                dfs(v, graph, state, bridges);
407
408                // Check if the subtree rooted at v has a back edge to an ancestor of u
409                let v_low = *state.low.get(&v).unwrap();
410                let u_low = *state.low.get(&u).unwrap();
411                state.low.insert(u, u_low.min(v_low));
412
413                // If low[v] > disc[u], then (u, v) is a bridge
414                let u_disc = *state.disc.get(&u).unwrap();
415                if v_low > u_disc {
416                    bridges.push((graph.inner()[u].clone(), graph.inner()[v].clone()));
417                }
418            } else if state.parent.get(&u).unwrap() != &Some(v) {
419                // Update low[u] for back edge
420                let v_disc = *state.disc.get(&v).unwrap();
421                let u_low = *state.low.get(&u).unwrap();
422                state.low.insert(u, u_low.min(v_disc));
423            }
424        }
425    }
426
427    let mut bridges = Vec::new();
428    let mut state = DfsState {
429        time: 0,
430        disc: HashMap::new(),
431        low: HashMap::new(),
432        parent: HashMap::new(),
433        visited: HashSet::new(),
434    };
435
436    for node in graph.inner().node_indices() {
437        if !state.visited.contains(&node) {
438            state.parent.insert(node, None);
439            dfs(node, graph, &mut state, &mut bridges);
440        }
441    }
442
443    bridges
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449    use crate::error::Result as GraphResult;
450    use crate::generators::create_graph;
451
452    #[test]
453    fn test_connected_components() -> GraphResult<()> {
454        // Create a graph with two components: {0, 1, 2} and {3, 4}
455        let mut graph = create_graph::<i32, ()>();
456
457        graph.add_edge(0, 1, ())?;
458        graph.add_edge(1, 2, ())?;
459        graph.add_edge(3, 4, ())?;
460
461        let components = connected_components(&graph);
462        assert_eq!(components.len(), 2);
463
464        // Check component sizes
465        let sizes: Vec<usize> = components.iter().map(|c| c.len()).collect();
466        assert!(sizes.contains(&3));
467        assert!(sizes.contains(&2));
468
469        Ok(())
470    }
471
472    #[test]
473    fn test_strongly_connected_components() -> GraphResult<()> {
474        // Create a directed graph with SCCs
475        let mut graph = crate::base::DiGraph::<&str, ()>::new();
476
477        // Create a cycle A -> B -> C -> A
478        graph.add_edge("A", "B", ())?;
479        graph.add_edge("B", "C", ())?;
480        graph.add_edge("C", "A", ())?;
481
482        // Add isolated node D by adding an edge from D to E
483        graph.add_edge("D", "E", ())?;
484
485        let sccs = strongly_connected_components(&graph);
486        assert_eq!(sccs.len(), 3);
487
488        // One SCC should have 3 nodes (A, B, C), two should have 1 (D and E)
489        let sizes: Vec<usize> = sccs.iter().map(|c| c.len()).collect();
490        assert!(sizes.contains(&3));
491        assert_eq!(sizes.iter().filter(|&&s| s == 1).count(), 2);
492
493        Ok(())
494    }
495
496    #[test]
497    fn test_articulation_points() -> GraphResult<()> {
498        // Create a graph where node 1 is an articulation point
499        let mut graph = create_graph::<i32, ()>();
500
501        // Structure: 0 - 1 - 2
502        //                |
503        //                3
504        graph.add_edge(0, 1, ())?;
505        graph.add_edge(1, 2, ())?;
506        graph.add_edge(1, 3, ())?;
507
508        let aps = articulation_points(&graph);
509        assert_eq!(aps.len(), 1);
510        assert!(aps.contains(&1));
511
512        Ok(())
513    }
514
515    #[test]
516    fn test_is_bipartite() -> GraphResult<()> {
517        // Create a bipartite graph (square)
518        let mut bipartite = create_graph::<i32, ()>();
519
520        bipartite.add_edge(0, 1, ())?;
521        bipartite.add_edge(1, 2, ())?;
522        bipartite.add_edge(2, 3, ())?;
523        bipartite.add_edge(3, 0, ())?;
524
525        let result = is_bipartite(&bipartite);
526        assert!(result.is_bipartite);
527        assert_eq!(result.coloring.len(), 4);
528
529        // Create a non-bipartite graph (triangle)
530        let mut non_bipartite = create_graph::<i32, ()>();
531
532        non_bipartite.add_edge(0, 1, ())?;
533        non_bipartite.add_edge(1, 2, ())?;
534        non_bipartite.add_edge(2, 0, ())?;
535
536        let result = is_bipartite(&non_bipartite);
537        assert!(!result.is_bipartite);
538
539        Ok(())
540    }
541
542    #[test]
543    fn test_weakly_connected_components() -> GraphResult<()> {
544        // Create a directed graph with two weakly connected components
545        let mut graph = crate::base::DiGraph::<&str, ()>::new();
546
547        // Component 1: A -> B -> C (weakly connected but not strongly connected)
548        graph.add_edge("A", "B", ())?;
549        graph.add_edge("B", "C", ())?;
550
551        // Component 2: D -> E <- F (triangle, weakly connected)
552        graph.add_edge("D", "E", ())?;
553        graph.add_edge("F", "E", ())?;
554        graph.add_edge("D", "F", ())?;
555
556        let wccs = weakly_connected_components(&graph);
557        assert_eq!(wccs.len(), 2);
558
559        // One component should have 3 nodes, one should have 3 nodes
560        let sizes: Vec<usize> = wccs.iter().map(|c| c.len()).collect();
561        assert!(sizes.contains(&3));
562        assert_eq!(sizes.iter().filter(|&&s| s == 3).count(), 2);
563
564        Ok(())
565    }
566
567    #[test]
568    fn test_bridges() -> GraphResult<()> {
569        // Create a graph with a bridge
570        let mut graph = create_graph::<i32, ()>();
571
572        // Create two triangles connected by a bridge
573        // Triangle 1: 0-1-2
574        graph.add_edge(0, 1, ())?;
575        graph.add_edge(1, 2, ())?;
576        graph.add_edge(2, 0, ())?;
577
578        // Bridge: 2-3
579        graph.add_edge(2, 3, ())?;
580
581        // Add another triangle to make bridge more obvious
582        graph.add_edge(3, 4, ())?;
583        graph.add_edge(4, 5, ())?;
584        graph.add_edge(5, 3, ())?;
585
586        let bridges_found = bridges(&graph);
587        assert_eq!(bridges_found.len(), 1);
588
589        // The bridge should be (2, 3) or (3, 2)
590        let bridge = &bridges_found[0];
591        assert!((bridge.0 == 2 && bridge.1 == 3) || (bridge.0 == 3 && bridge.1 == 2));
592
593        Ok(())
594    }
595}