Skip to main content

rust_igraph/algorithms/properties/
is_apex_tree.rs

1//! Apex tree predicate (ALGO-PR-128).
2//!
3//! A graph is an apex tree if there exists a vertex whose removal
4//! makes the remaining graph a tree. This is stricter than apex
5//! forest (which requires only a forest after removal).
6//!
7//! Every tree is trivially an apex tree (remove any leaf).
8//! Every graph with exactly one cycle where the cycle passes
9//! through some vertex is an apex tree.
10//!
11//! Directed graphs are treated as undirected.
12
13use crate::algorithms::paths::dijkstra::DijkstraMode;
14use crate::algorithms::properties::is_tree::is_tree;
15use crate::core::{Graph, IgraphResult};
16
17/// Check whether a graph is an apex tree.
18///
19/// A graph is an apex tree if removing some single vertex yields
20/// a tree. Tests each vertex as a candidate apex.
21///
22/// Directed graphs are treated as undirected.
23///
24/// # Examples
25///
26/// ```
27/// use rust_igraph::{Graph, is_apex_tree};
28///
29/// // Triangle: remove any vertex → single edge (tree) ✓
30/// let mut g = Graph::with_vertices(3);
31/// g.add_edge(0, 1).unwrap();
32/// g.add_edge(1, 2).unwrap();
33/// g.add_edge(2, 0).unwrap();
34/// assert!(is_apex_tree(&g).unwrap());
35///
36/// // Two disjoint triangles: no single vertex removal yields tree
37/// let mut g = Graph::with_vertices(6);
38/// g.add_edge(0, 1).unwrap();
39/// g.add_edge(1, 2).unwrap();
40/// g.add_edge(2, 0).unwrap();
41/// g.add_edge(3, 4).unwrap();
42/// g.add_edge(4, 5).unwrap();
43/// g.add_edge(5, 3).unwrap();
44/// assert!(!is_apex_tree(&g).unwrap());
45/// ```
46pub fn is_apex_tree(graph: &Graph) -> IgraphResult<bool> {
47    let n = graph.vcount();
48
49    if n <= 1 {
50        return Ok(true);
51    }
52
53    if is_tree(graph, DijkstraMode::All)?.is_some() {
54        return Ok(true);
55    }
56
57    let n_usize = n as usize;
58    let mut adj = vec![vec![false; n_usize]; n_usize];
59    for v in 0..n {
60        let nbrs = graph.neighbors(v)?;
61        for &w in &nbrs {
62            adj[v as usize][w as usize] = true;
63            adj[w as usize][v as usize] = true;
64        }
65    }
66
67    for apex in 0..n_usize {
68        if is_tree_without(&adj, apex, n_usize) {
69            return Ok(true);
70        }
71    }
72
73    Ok(false)
74}
75
76fn is_tree_without(adj: &[Vec<bool>], removed: usize, n: usize) -> bool {
77    let remaining = n - 1;
78    if remaining == 0 {
79        return true;
80    }
81
82    let mut visited = vec![false; n];
83    visited[removed] = true;
84
85    let Some(start) = (0..n).find(|&v| v != removed) else {
86        return false;
87    };
88    let mut stack = vec![start];
89    visited[start] = true;
90    let mut visited_count = 1usize;
91    let mut edge_count = 0usize;
92
93    while let Some(u) = stack.pop() {
94        for (v, &is_adj) in adj[u].iter().enumerate() {
95            if v == removed || !is_adj {
96                continue;
97            }
98            edge_count += 1;
99            if !visited[v] {
100                visited[v] = true;
101                visited_count += 1;
102                stack.push(v);
103            }
104        }
105    }
106
107    edge_count /= 2;
108
109    // Tree iff connected (visited_count == remaining) and edges == remaining - 1
110    visited_count == remaining && edge_count == remaining - 1
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn empty_graph() {
119        let g = Graph::with_vertices(0);
120        assert!(is_apex_tree(&g).unwrap());
121    }
122
123    #[test]
124    fn single_vertex() {
125        let g = Graph::with_vertices(1);
126        assert!(is_apex_tree(&g).unwrap());
127    }
128
129    #[test]
130    fn single_edge() {
131        let mut g = Graph::with_vertices(2);
132        g.add_edge(0, 1).unwrap();
133        assert!(is_apex_tree(&g).unwrap());
134    }
135
136    #[test]
137    fn triangle() {
138        let mut g = Graph::with_vertices(3);
139        g.add_edge(0, 1).unwrap();
140        g.add_edge(1, 2).unwrap();
141        g.add_edge(2, 0).unwrap();
142        assert!(is_apex_tree(&g).unwrap());
143    }
144
145    #[test]
146    fn tree() {
147        let mut g = Graph::with_vertices(5);
148        g.add_edge(0, 1).unwrap();
149        g.add_edge(1, 2).unwrap();
150        g.add_edge(1, 3).unwrap();
151        g.add_edge(3, 4).unwrap();
152        assert!(is_apex_tree(&g).unwrap());
153    }
154
155    #[test]
156    fn c4() {
157        // C_4: remove any vertex → P_3 (tree) ✓
158        let mut g = Graph::with_vertices(4);
159        g.add_edge(0, 1).unwrap();
160        g.add_edge(1, 2).unwrap();
161        g.add_edge(2, 3).unwrap();
162        g.add_edge(3, 0).unwrap();
163        assert!(is_apex_tree(&g).unwrap());
164    }
165
166    #[test]
167    fn k4() {
168        // K_4: remove any vertex → K_3 (triangle, NOT a tree).
169        let mut g = Graph::with_vertices(4);
170        for i in 0..4u32 {
171            for j in (i + 1)..4 {
172                g.add_edge(i, j).unwrap();
173            }
174        }
175        assert!(!is_apex_tree(&g).unwrap());
176    }
177
178    #[test]
179    fn two_triangles_not_apex_tree() {
180        let mut g = Graph::with_vertices(6);
181        g.add_edge(0, 1).unwrap();
182        g.add_edge(1, 2).unwrap();
183        g.add_edge(2, 0).unwrap();
184        g.add_edge(3, 4).unwrap();
185        g.add_edge(4, 5).unwrap();
186        g.add_edge(5, 3).unwrap();
187        assert!(!is_apex_tree(&g).unwrap());
188    }
189
190    #[test]
191    fn two_triangles_shared_vertex() {
192        // 0-1-2-0 and 0-3-4-0. Remove 0 → {1,2,3,4} with edges 1-2, 3-4.
193        // That's a forest, NOT a tree (two components).
194        // So NOT apex tree (but IS apex forest).
195        let mut g = Graph::with_vertices(5);
196        g.add_edge(0, 1).unwrap();
197        g.add_edge(1, 2).unwrap();
198        g.add_edge(2, 0).unwrap();
199        g.add_edge(0, 3).unwrap();
200        g.add_edge(3, 4).unwrap();
201        g.add_edge(4, 0).unwrap();
202        assert!(!is_apex_tree(&g).unwrap());
203    }
204
205    #[test]
206    fn diamond() {
207        // Diamond: 0-1,0-2,0-3,1-2,1-3 (K_4 minus 2-3).
208        // Remove 0: {1,2,3} with edges 1-2,1-3 → star, which is a tree ✓
209        let mut g = Graph::with_vertices(4);
210        g.add_edge(0, 1).unwrap();
211        g.add_edge(0, 2).unwrap();
212        g.add_edge(0, 3).unwrap();
213        g.add_edge(1, 2).unwrap();
214        g.add_edge(1, 3).unwrap();
215        assert!(is_apex_tree(&g).unwrap());
216    }
217
218    #[test]
219    fn theta_graph() {
220        // Theta: 0-1-3 and 0-2-3 (two paths between 0 and 3).
221        // Remove 0 → {1,2,3} with edges 1-3,2-3 → star on 3 → tree ✓
222        let mut g = Graph::with_vertices(4);
223        g.add_edge(0, 1).unwrap();
224        g.add_edge(1, 3).unwrap();
225        g.add_edge(0, 2).unwrap();
226        g.add_edge(2, 3).unwrap();
227        assert!(is_apex_tree(&g).unwrap());
228    }
229
230    #[test]
231    fn edgeless_not_apex_tree() {
232        // 4 isolated vertices: removing any one leaves 3 isolated vertices (forest, not tree)
233        let g = Graph::with_vertices(4);
234        assert!(!is_apex_tree(&g).unwrap());
235    }
236
237    #[test]
238    fn two_isolated() {
239        // 2 isolated: removing one yields single vertex (tree) ✓
240        let g = Graph::with_vertices(2);
241        assert!(is_apex_tree(&g).unwrap());
242    }
243
244    #[test]
245    fn directed_treated_as_undirected() {
246        let mut g = Graph::new(3, true).unwrap();
247        g.add_edge(0, 1).unwrap();
248        g.add_edge(1, 2).unwrap();
249        g.add_edge(2, 0).unwrap();
250        assert!(is_apex_tree(&g).unwrap());
251    }
252
253    #[test]
254    fn wheel_w4() {
255        // W_4: center 0, rim 1-2-3-4-1. Remove 0 → C_4 (not a tree).
256        // Remove rim vertex 1 → 0 connected to 2,3,4; plus 2-3,3-4.
257        // Edges: 0-2,0-3,0-4,2-3,3-4. 4 vertices, 5 edges. Not tree (4 verts need 3 edges).
258        // NOT apex tree.
259        let mut g = Graph::with_vertices(5);
260        g.add_edge(0, 1).unwrap();
261        g.add_edge(0, 2).unwrap();
262        g.add_edge(0, 3).unwrap();
263        g.add_edge(0, 4).unwrap();
264        g.add_edge(1, 2).unwrap();
265        g.add_edge(2, 3).unwrap();
266        g.add_edge(3, 4).unwrap();
267        g.add_edge(4, 1).unwrap();
268        assert!(!is_apex_tree(&g).unwrap());
269    }
270
271    #[test]
272    fn tree_with_extra_edge() {
273        // Tree 0-1-2-3 plus edge 0-2. Only one cycle through 0-1-2.
274        // Remove 0: {1,2,3} with edges 1-2,2-3 → path → tree ✓
275        let mut g = Graph::with_vertices(4);
276        g.add_edge(0, 1).unwrap();
277        g.add_edge(1, 2).unwrap();
278        g.add_edge(2, 3).unwrap();
279        g.add_edge(0, 2).unwrap();
280        assert!(is_apex_tree(&g).unwrap());
281    }
282}