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}