retworkx_core/connectivity/
biconnected.rs

1// Licensed under the Apache License, Version 2.0 (the "License"); you may
2// not use this file except in compliance with the License. You may obtain
3// a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10// License for the specific language governing permissions and limitations
11// under the License.
12
13use hashbrown::{HashMap, HashSet};
14use std::hash::Hash;
15
16use petgraph::{
17    visit::{
18        EdgeCount, GraphBase, GraphProp, IntoEdges, IntoNodeIdentifiers,
19        NodeIndexable, Time, Visitable,
20    },
21    Undirected,
22};
23
24use crate::traversal::{depth_first_search, DfsEvent};
25
26const NULL: usize = std::usize::MAX;
27
28type Edge<G> = (<G as GraphBase>::NodeId, <G as GraphBase>::NodeId);
29
30#[inline]
31fn is_root(parent: &[usize], u: usize) -> bool {
32    parent[u] == NULL
33}
34
35/// Return the articulation points of an undirected graph.
36///
37/// An articulation point or cut vertex is any node whose removal (along with
38/// all its incident edges) increases the number of connected components of
39/// a graph. An undirected connected graph without articulation points is
40/// biconnected.
41///
42/// At the same time, you can record the biconnected components in `components`.
43///
44/// Biconnected components are maximal subgraphs such that the removal
45/// of a node (and all edges incident on that node) will not disconnect
46/// the subgraph. Note that nodes may be part of more than one biconnected
47/// component. Those nodes are articulation points, or cut vertices. The
48/// algorithm computes how many biconnected components are in the graph,
49/// and assigning each component an integer label.
50///
51/// # Note
52/// The function implicitly assumes that there are no parallel edges
53/// or self loops. It may produce incorrect/unexpected results if the
54/// input graph has self loops or parallel edges.
55///
56///
57/// # Example:
58/// ```rust
59/// use std::iter::FromIterator;
60/// use hashbrown::{HashMap, HashSet};
61///
62/// use retworkx_core::connectivity::articulation_points;
63/// use retworkx_core::petgraph::graph::UnGraph;
64/// use retworkx_core::petgraph::graph::node_index as nx;
65///
66/// let graph = UnGraph::<(), ()>::from_edges(&[
67///    (0, 1), (0, 2), (1, 2), (1, 3),
68/// ]);
69///
70/// let mut bicomp = HashMap::new();
71/// let a_points = articulation_points(&graph, Some(&mut bicomp));
72///
73/// assert_eq!(a_points, HashSet::from_iter([nx(1)]));
74/// assert_eq!(bicomp, HashMap::from_iter([
75///     ((nx(0), nx(2)), 1), ((nx(2), nx(1)), 1), ((nx(1), nx(0)), 1),
76///     ((nx(1), nx(3)), 0)
77/// ]));
78/// ```
79pub fn articulation_points<G>(
80    graph: G,
81    components: Option<&mut HashMap<Edge<G>, usize>>,
82) -> HashSet<G::NodeId>
83where
84    G: GraphProp<EdgeType = Undirected>
85        + EdgeCount
86        + IntoEdges
87        + Visitable
88        + NodeIndexable
89        + IntoNodeIdentifiers,
90    G::NodeId: Eq + Hash,
91{
92    let num_nodes = graph.node_bound();
93
94    let mut low = vec![NULL; num_nodes];
95    let mut disc = vec![NULL; num_nodes];
96    let mut parent = vec![NULL; num_nodes];
97
98    let mut root_children: usize = 0;
99    let mut points = HashSet::new();
100
101    let mut edge_stack = Vec::new();
102    let mut tmp_components = if components.is_some() {
103        HashMap::with_capacity(graph.edge_count())
104    } else {
105        HashMap::new()
106    };
107    let mut num_components: usize = 0;
108
109    depth_first_search(graph, graph.node_identifiers(), |event| match event {
110        DfsEvent::Discover(u_id, Time(t)) => {
111            let u = graph.to_index(u_id);
112            low[u] = t;
113            disc[u] = t;
114        }
115        DfsEvent::TreeEdge(u_id, v_id, _) => {
116            let u = graph.to_index(u_id);
117            let v = graph.to_index(v_id);
118            parent[v] = u;
119            if is_root(&parent, u) {
120                root_children += 1;
121            }
122            if components.is_some() {
123                edge_stack.push((u_id, v_id));
124            }
125        }
126        DfsEvent::BackEdge(u_id, v_id, _) => {
127            let u = graph.to_index(u_id);
128            let v = graph.to_index(v_id);
129
130            // do *not* consider ``(u, v)`` as a back edge if ``(v, u)`` is a tree edge.
131            if v != parent[u] {
132                low[u] = low[u].min(disc[v]);
133                if components.is_some() {
134                    edge_stack.push((u_id, v_id));
135                }
136            }
137        }
138        DfsEvent::Finish(u_id, _) => {
139            let u = graph.to_index(u_id);
140            if is_root(&parent, u) {
141                if root_children > 1 {
142                    points.insert(u_id);
143                }
144                // restart ``root_children`` for the remaining connected components
145                root_children = 0;
146            } else {
147                let pu = parent[u];
148                let pu_id = graph.from_index(pu);
149                low[pu] = low[pu].min(low[u]);
150
151                if !is_root(&parent, pu) && low[u] >= disc[pu] {
152                    points.insert(pu_id);
153                    // now find a biconnected component that the
154                    // current articulation point belongs.
155                    if components.is_some() {
156                        if let Some(at) =
157                            edge_stack.iter().rposition(|&x| x == (pu_id, u_id))
158                        {
159                            tmp_components.extend(
160                                edge_stack[at..]
161                                    .iter()
162                                    .map(|edge| (*edge, num_components)),
163                            );
164                            edge_stack.truncate(at);
165                            num_components += 1;
166                        }
167                    }
168                }
169
170                if is_root(&parent, pu) && components.is_some() {
171                    if let Some(at) =
172                        edge_stack.iter().position(|&x| x == (pu_id, u_id))
173                    {
174                        tmp_components.extend(
175                            edge_stack[at..]
176                                .iter()
177                                .map(|edge| (*edge, num_components)),
178                        );
179                        edge_stack.truncate(at);
180                        num_components += 1;
181                    }
182                }
183            }
184        }
185        _ => {}
186    });
187
188    if let Some(x) = components {
189        *x = tmp_components;
190    }
191
192    points
193}