rust_igraph/algorithms/properties/
is_tree.rs1use crate::algorithms::paths::dijkstra::DijkstraMode;
24use crate::core::Graph;
25use crate::core::error::IgraphResult;
26use crate::core::graph::{EdgeId, VertexId};
27
28pub fn is_tree(graph: &Graph, mode: DijkstraMode) -> IgraphResult<Option<VertexId>> {
69 let n = graph.vcount();
70 let m = graph.ecount();
71
72 if n == 0 {
73 return Ok(None);
74 }
75
76 if m as u64 != u64::from(n).saturating_sub(1) {
78 return Ok(None);
79 }
80
81 if n == 1 {
83 return Ok(Some(0));
84 }
85
86 let directed = graph.is_directed();
87 let effective_mode = if directed { mode } else { DijkstraMode::All };
88
89 let Some(root) = pick_root(graph, n, effective_mode)? else {
90 return Ok(None);
91 };
92
93 let visited_count = dfs_count(graph, n, root, effective_mode, directed)?;
98 if visited_count == n {
99 Ok(Some(root))
100 } else {
101 Ok(None)
102 }
103}
104
105fn pick_root(graph: &Graph, n: VertexId, mode: DijkstraMode) -> IgraphResult<Option<VertexId>> {
109 match mode {
110 DijkstraMode::All => Ok(Some(0)),
111 DijkstraMode::Out => find_zero_degree_root(graph, n, true),
112 DijkstraMode::In => find_zero_degree_root(graph, n, false),
113 }
114}
115
116fn find_zero_degree_root(
120 graph: &Graph,
121 n: VertexId,
122 use_in_degree: bool,
123) -> IgraphResult<Option<VertexId>> {
124 for v in 0..n {
125 let d = if use_in_degree {
126 graph.incident_in(v)?.len()
127 } else {
128 graph.incident(v)?.len()
129 };
130 if d == 0 {
131 return Ok(Some(v));
132 }
133 if d > 1 {
134 return Ok(None);
135 }
136 }
137 Ok(None)
138}
139
140fn dfs_count(
143 graph: &Graph,
144 n: VertexId,
145 root: VertexId,
146 mode: DijkstraMode,
147 directed: bool,
148) -> IgraphResult<u32> {
149 let n_us = n as usize;
150 let mut visited: Vec<bool> = vec![false; n_us];
151 let mut stack: Vec<VertexId> = Vec::with_capacity(n_us);
152 let mut count: u32 = 0;
153
154 stack.push(root);
155 while let Some(u) = stack.pop() {
156 if visited[u as usize] {
157 continue;
158 }
159 visited[u as usize] = true;
160 count = count.saturating_add(1);
161
162 for eid in incident_edges(graph, u, mode, directed)? {
163 let nei = graph.edge_other(eid, u)?;
164 if !visited[nei as usize] {
165 stack.push(nei);
166 }
167 }
168 }
169 Ok(count)
170}
171
172fn incident_edges(
174 graph: &Graph,
175 u: VertexId,
176 mode: DijkstraMode,
177 directed: bool,
178) -> IgraphResult<Vec<EdgeId>> {
179 match mode {
180 DijkstraMode::Out => graph.incident(u),
181 DijkstraMode::In => graph.incident_in(u),
182 DijkstraMode::All => {
183 if directed {
184 let mut out = graph.incident(u)?;
185 out.extend(graph.incident_in(u)?);
186 Ok(out)
187 } else {
188 graph.incident(u)
189 }
190 }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
201 fn null_graph_is_not_a_tree() {
202 let g = Graph::with_vertices(0);
203 assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), None);
204 }
205
206 #[test]
207 fn single_vertex_undirected_is_a_tree() {
208 let g = Graph::with_vertices(1);
209 assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), Some(0));
210 }
211
212 #[test]
213 fn single_vertex_directed_out_is_a_tree() {
214 let g = Graph::new(1, true).unwrap();
215 assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), Some(0));
216 }
217
218 #[test]
221 fn undirected_path_is_a_tree() {
222 let mut g = Graph::with_vertices(4);
223 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
224 assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), Some(0));
225 }
226
227 #[test]
228 fn undirected_star_is_a_tree() {
229 let mut g = Graph::with_vertices(5);
230 g.add_edges(vec![(0u32, 1u32), (0, 2), (0, 3), (0, 4)])
231 .unwrap();
232 assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), Some(0));
233 }
234
235 #[test]
236 fn undirected_triangle_is_not_a_tree() {
237 let mut g = Graph::with_vertices(3);
238 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
239 assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), None);
240 }
241
242 #[test]
243 fn undirected_disconnected_is_not_a_tree() {
244 let mut g = Graph::with_vertices(4);
246 g.add_edges(vec![(0u32, 1u32), (2, 3)]).unwrap();
247 assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), None);
248 }
249
250 #[test]
251 fn undirected_disconnected_correct_edge_count_is_not_a_tree() {
252 let mut g = Graph::with_vertices(3);
255 g.add_edge(0, 1).unwrap();
256 g.add_edge(0, 1).unwrap();
257 assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), None);
258 }
259
260 #[test]
261 fn undirected_self_loop_breaks_edge_count() {
262 let mut g = Graph::with_vertices(3);
266 g.add_edge(0, 0).unwrap();
267 g.add_edge(1, 2).unwrap();
268 assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), None);
269 }
270
271 #[test]
274 fn directed_out_arborescence_is_a_tree() {
275 let mut g = Graph::new(4, true).unwrap();
277 g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 3)]).unwrap();
278 assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), Some(0));
279 }
280
281 #[test]
282 fn directed_out_chain_is_a_tree() {
283 let mut g = Graph::new(4, true).unwrap();
285 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
286 assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), Some(0));
287 }
288
289 #[test]
290 fn directed_in_arborescence_is_not_an_out_tree() {
291 let mut g = Graph::new(4, true).unwrap();
293 g.add_edges(vec![(1u32, 0u32), (2, 0), (3, 1)]).unwrap();
294 assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), None);
295 assert_eq!(is_tree(&g, DijkstraMode::In).unwrap(), Some(0));
296 }
297
298 #[test]
299 fn directed_v_pattern_to_center_is_not_an_out_tree() {
300 let mut g = Graph::new(3, true).unwrap();
302 g.add_edges(vec![(0u32, 2u32), (1, 2)]).unwrap();
303 assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), None);
304 }
305
306 #[test]
307 fn directed_cycle_is_not_a_tree() {
308 let mut g = Graph::new(3, true).unwrap();
310 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
311 assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), None);
312 assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), None);
313 }
314
315 #[test]
316 fn directed_all_mode_treats_as_undirected() {
317 let mut g = Graph::new(4, true).unwrap();
319 g.add_edges(vec![(0u32, 1u32), (2, 1), (2, 3)]).unwrap();
320 assert_eq!(is_tree(&g, DijkstraMode::Out).unwrap(), None);
321 assert_eq!(is_tree(&g, DijkstraMode::All).unwrap(), Some(0));
322 }
323}