rust_igraph/algorithms/properties/
is_distance_hereditary.rs1use crate::algorithms::connectivity::components::connected_components;
20use crate::core::{Graph, IgraphResult};
21
22pub fn is_distance_hereditary(graph: &Graph) -> IgraphResult<bool> {
57 if graph.is_directed() {
58 return Ok(false);
59 }
60
61 let n = graph.vcount() as usize;
62 if n <= 2 {
63 return Ok(true);
64 }
65
66 let cc = connected_components(graph)?;
67 for comp_id in 0..cc.count {
68 let comp_verts: Vec<u32> = (0..graph.vcount())
69 .filter(|&v| cc.membership[v as usize] == comp_id)
70 .collect();
71 if !is_component_distance_hereditary(graph, &comp_verts)? {
72 return Ok(false);
73 }
74 }
75
76 Ok(true)
77}
78
79fn is_component_distance_hereditary(graph: &Graph, vertices: &[u32]) -> IgraphResult<bool> {
80 if vertices.len() <= 2 {
81 return Ok(true);
82 }
83
84 let n = graph.vcount() as usize;
85 let mut removed = vec![false; n];
86 let mut remaining = vertices.len();
87
88 loop {
89 if remaining <= 2 {
90 return Ok(true);
91 }
92
93 let mut progress = false;
94
95 for &v in vertices {
96 if removed[v as usize] {
97 continue;
98 }
99
100 let nbrs = graph.neighbors(v)?;
101 let active_nbrs: Vec<u32> =
102 nbrs.into_iter().filter(|&w| !removed[w as usize]).collect();
103 let deg = active_nbrs.len();
104
105 if deg == 0 {
106 removed[v as usize] = true;
107 remaining = remaining.saturating_sub(1);
108 progress = true;
109 continue;
110 }
111
112 if deg == 1 {
113 removed[v as usize] = true;
114 remaining = remaining.saturating_sub(1);
115 progress = true;
116 continue;
117 }
118
119 if find_and_remove_twin(graph, v, &active_nbrs, &mut removed, n)? {
120 remaining = remaining.saturating_sub(1);
121 progress = true;
122 }
123 }
124
125 if !progress {
126 return Ok(false);
127 }
128 }
129}
130
131fn find_and_remove_twin(
132 graph: &Graph,
133 v: u32,
134 v_nbrs: &[u32],
135 removed: &mut [bool],
136 _n: usize,
137) -> IgraphResult<bool> {
138 let mut v_nbr_set: Vec<u32> = v_nbrs.to_vec();
139 v_nbr_set.sort_unstable();
140
141 for &w in v_nbrs {
142 if removed[w as usize] || w <= v {
143 continue;
144 }
145
146 let w_all_nbrs = graph.neighbors(w)?;
147 let mut w_nbrs: Vec<u32> = w_all_nbrs
148 .into_iter()
149 .filter(|&u| !removed[u as usize])
150 .collect();
151 w_nbrs.sort_unstable();
152
153 let v_without_w: Vec<u32> = v_nbr_set.iter().copied().filter(|&x| x != w).collect();
155 let w_without_v: Vec<u32> = w_nbrs.iter().copied().filter(|&x| x != v).collect();
156 if v_without_w == w_without_v {
157 removed[v as usize] = true;
158 return Ok(true);
159 }
160 }
161
162 for &candidate in v_nbrs {
164 if removed[candidate as usize] {
165 continue;
166 }
167 let candidate_nbrs = graph.neighbors(candidate)?;
168 for &w in &candidate_nbrs {
169 if removed[w as usize] || w == v || w <= v {
170 continue;
171 }
172 if v_nbr_set.binary_search(&w).is_ok() {
173 continue;
174 }
175
176 let w_all_nbrs = graph.neighbors(w)?;
177 let mut w_nbrs: Vec<u32> = w_all_nbrs
178 .into_iter()
179 .filter(|&u| !removed[u as usize])
180 .collect();
181 w_nbrs.sort_unstable();
182
183 if v_nbr_set == w_nbrs {
184 removed[v as usize] = true;
185 return Ok(true);
186 }
187 }
188 }
189
190 Ok(false)
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn empty_graph() {
199 let g = Graph::with_vertices(0);
200 assert!(is_distance_hereditary(&g).unwrap());
201 }
202
203 #[test]
204 fn single_vertex() {
205 let g = Graph::with_vertices(1);
206 assert!(is_distance_hereditary(&g).unwrap());
207 }
208
209 #[test]
210 fn single_edge() {
211 let mut g = Graph::with_vertices(2);
212 g.add_edge(0, 1).unwrap();
213 assert!(is_distance_hereditary(&g).unwrap());
214 }
215
216 #[test]
217 fn path_is_dh() {
218 let mut g = Graph::with_vertices(5);
219 g.add_edge(0, 1).unwrap();
220 g.add_edge(1, 2).unwrap();
221 g.add_edge(2, 3).unwrap();
222 g.add_edge(3, 4).unwrap();
223 assert!(is_distance_hereditary(&g).unwrap());
224 }
225
226 #[test]
227 fn tree_is_dh() {
228 let mut g = Graph::with_vertices(7);
229 g.add_edge(0, 1).unwrap();
230 g.add_edge(0, 2).unwrap();
231 g.add_edge(1, 3).unwrap();
232 g.add_edge(1, 4).unwrap();
233 g.add_edge(2, 5).unwrap();
234 g.add_edge(2, 6).unwrap();
235 assert!(is_distance_hereditary(&g).unwrap());
236 }
237
238 #[test]
239 fn triangle_is_dh() {
240 let mut g = Graph::with_vertices(3);
241 g.add_edge(0, 1).unwrap();
242 g.add_edge(1, 2).unwrap();
243 g.add_edge(2, 0).unwrap();
244 assert!(is_distance_hereditary(&g).unwrap());
245 }
246
247 #[test]
248 fn k4_is_dh() {
249 let mut g = Graph::with_vertices(4);
250 for u in 0..4u32 {
251 for v in (u + 1)..4 {
252 g.add_edge(u, v).unwrap();
253 }
254 }
255 assert!(is_distance_hereditary(&g).unwrap());
256 }
257
258 #[test]
259 fn star_is_dh() {
260 let mut g = Graph::with_vertices(5);
261 for i in 1..5u32 {
262 g.add_edge(0, i).unwrap();
263 }
264 assert!(is_distance_hereditary(&g).unwrap());
265 }
266
267 #[test]
268 fn cycle_4_is_dh() {
269 let mut g = Graph::with_vertices(4);
271 g.add_edge(0, 1).unwrap();
272 g.add_edge(1, 2).unwrap();
273 g.add_edge(2, 3).unwrap();
274 g.add_edge(3, 0).unwrap();
275 assert!(is_distance_hereditary(&g).unwrap());
276 }
277
278 #[test]
279 fn cycle_5_not_dh() {
280 let mut g = Graph::with_vertices(5);
281 g.add_edge(0, 1).unwrap();
282 g.add_edge(1, 2).unwrap();
283 g.add_edge(2, 3).unwrap();
284 g.add_edge(3, 4).unwrap();
285 g.add_edge(4, 0).unwrap();
286 assert!(!is_distance_hereditary(&g).unwrap());
287 }
288
289 #[test]
290 fn cycle_6_not_dh() {
291 let mut g = Graph::with_vertices(6);
292 g.add_edge(0, 1).unwrap();
293 g.add_edge(1, 2).unwrap();
294 g.add_edge(2, 3).unwrap();
295 g.add_edge(3, 4).unwrap();
296 g.add_edge(4, 5).unwrap();
297 g.add_edge(5, 0).unwrap();
298 assert!(!is_distance_hereditary(&g).unwrap());
299 }
300
301 #[test]
302 fn house_graph_not_dh() {
303 let mut g = Graph::with_vertices(5);
310 g.add_edge(0, 1).unwrap();
311 g.add_edge(1, 2).unwrap();
312 g.add_edge(2, 3).unwrap();
313 g.add_edge(3, 0).unwrap();
314 g.add_edge(2, 4).unwrap();
315 g.add_edge(3, 4).unwrap();
316 assert!(!is_distance_hereditary(&g).unwrap());
317 }
318
319 #[test]
320 fn edgeless_is_dh() {
321 let g = Graph::with_vertices(5);
322 assert!(is_distance_hereditary(&g).unwrap());
323 }
324
325 #[test]
326 fn disconnected_trees_dh() {
327 let mut g = Graph::with_vertices(6);
328 g.add_edge(0, 1).unwrap();
329 g.add_edge(1, 2).unwrap();
330 g.add_edge(3, 4).unwrap();
331 g.add_edge(4, 5).unwrap();
332 assert!(is_distance_hereditary(&g).unwrap());
333 }
334
335 #[test]
336 fn directed_returns_false() {
337 let mut g = Graph::new(3, true).unwrap();
338 g.add_edge(0, 1).unwrap();
339 g.add_edge(1, 2).unwrap();
340 assert!(!is_distance_hereditary(&g).unwrap());
341 }
342
343 #[test]
344 fn two_triangles_sharing_vertex() {
345 let mut g = Graph::with_vertices(5);
347 g.add_edge(0, 1).unwrap();
348 g.add_edge(1, 2).unwrap();
349 g.add_edge(2, 0).unwrap();
350 g.add_edge(2, 3).unwrap();
351 g.add_edge(3, 4).unwrap();
352 g.add_edge(4, 2).unwrap();
353 assert!(is_distance_hereditary(&g).unwrap());
354 }
355
356 #[test]
357 fn gem_not_dh() {
358 let mut g = Graph::with_vertices(5);
360 g.add_edge(0, 1).unwrap();
361 g.add_edge(1, 2).unwrap();
362 g.add_edge(2, 3).unwrap();
363 g.add_edge(4, 0).unwrap();
364 g.add_edge(4, 1).unwrap();
365 g.add_edge(4, 2).unwrap();
366 g.add_edge(4, 3).unwrap();
367 assert!(!is_distance_hereditary(&g).unwrap());
368 }
369}