rust_igraph/algorithms/properties/
is_fork_free.rs1use crate::core::{Graph, IgraphResult};
14
15pub fn is_fork_free(graph: &Graph) -> IgraphResult<bool> {
44 if graph.is_directed() {
45 return Ok(false);
46 }
47
48 let n = graph.vcount();
49 if n < 5 {
50 return Ok(true);
51 }
52
53 let n_usize = n as usize;
54 let mut adj = vec![vec![false; n_usize]; n_usize];
55 let mut nbrs_list: Vec<Vec<u32>> = Vec::with_capacity(n_usize);
56
57 for v in 0..n {
58 let nbrs = graph.neighbors(v)?;
59 for &w in &nbrs {
60 adj[v as usize][w as usize] = true;
61 }
62 nbrs_list.push(nbrs);
63 }
64
65 for c in 0..n {
74 let ci = c as usize;
75 let cn = &nbrs_list[ci];
76 if cn.len() < 3 {
77 continue;
78 }
79
80 for (i, &a) in cn.iter().enumerate() {
81 let ai = a as usize;
82 for (j, &b) in cn.iter().enumerate().skip(i + 1) {
83 let bi = b as usize;
84 if adj[ai][bi] {
85 continue;
86 }
87 for &d in &cn[(j + 1)..] {
88 let di = d as usize;
89 if adj[ai][di] || adj[bi][di] {
90 continue;
91 }
92 if has_fork_pendant(&adj, &nbrs_list, ai, ci, bi, di) {
94 return Ok(false);
95 }
96 if has_fork_pendant(&adj, &nbrs_list, bi, ci, ai, di) {
97 return Ok(false);
98 }
99 if has_fork_pendant(&adj, &nbrs_list, di, ci, ai, bi) {
100 return Ok(false);
101 }
102 }
103 }
104 }
105 }
106
107 Ok(true)
108}
109
110fn has_fork_pendant(
112 adj: &[Vec<bool>],
113 nbrs_list: &[Vec<u32>],
114 arm: usize,
115 center: usize,
116 other1: usize,
117 other2: usize,
118) -> bool {
119 for &e_u32 in &nbrs_list[arm] {
120 let e = e_u32 as usize;
121 if e == center || e == other1 || e == other2 {
122 continue;
123 }
124 if !adj[center][e] && !adj[other1][e] && !adj[other2][e] {
125 return true;
126 }
127 }
128 false
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn empty_graph() {
137 let g = Graph::with_vertices(0);
138 assert!(is_fork_free(&g).unwrap());
139 }
140
141 #[test]
142 fn small_graphs() {
143 let g = Graph::with_vertices(4);
144 assert!(is_fork_free(&g).unwrap());
145 }
146
147 #[test]
148 fn triangle() {
149 let mut g = Graph::with_vertices(3);
150 g.add_edge(0, 1).unwrap();
151 g.add_edge(1, 2).unwrap();
152 g.add_edge(2, 0).unwrap();
153 assert!(is_fork_free(&g).unwrap());
154 }
155
156 #[test]
157 fn fork() {
158 let mut g = Graph::with_vertices(5);
160 g.add_edge(0, 1).unwrap();
161 g.add_edge(0, 2).unwrap();
162 g.add_edge(0, 3).unwrap();
163 g.add_edge(3, 4).unwrap();
164 assert!(!is_fork_free(&g).unwrap());
165 }
166
167 #[test]
168 fn claw_is_fork_free() {
169 let mut g = Graph::with_vertices(4);
171 g.add_edge(0, 1).unwrap();
172 g.add_edge(0, 2).unwrap();
173 g.add_edge(0, 3).unwrap();
174 assert!(is_fork_free(&g).unwrap());
175 }
176
177 #[test]
178 fn claw_with_pendant_is_fork() {
179 let mut g = Graph::with_vertices(5);
181 g.add_edge(0, 1).unwrap();
182 g.add_edge(0, 2).unwrap();
183 g.add_edge(0, 3).unwrap();
184 g.add_edge(1, 4).unwrap();
185 assert!(!is_fork_free(&g).unwrap());
186 }
187
188 #[test]
189 fn k5_fork_free() {
190 let mut g = Graph::with_vertices(5);
192 for i in 0..5u32 {
193 for j in (i + 1)..5 {
194 g.add_edge(i, j).unwrap();
195 }
196 }
197 assert!(is_fork_free(&g).unwrap());
198 }
199
200 #[test]
201 fn path_p5_not_fork_free() {
202 let mut g = Graph::with_vertices(5);
205 g.add_edge(0, 1).unwrap();
206 g.add_edge(1, 2).unwrap();
207 g.add_edge(2, 3).unwrap();
208 g.add_edge(3, 4).unwrap();
209 assert!(is_fork_free(&g).unwrap());
210 }
211
212 #[test]
213 fn star_with_subdivided_edge() {
214 let mut g = Graph::with_vertices(6);
222 g.add_edge(0, 5).unwrap();
223 g.add_edge(5, 1).unwrap();
224 g.add_edge(0, 2).unwrap();
225 g.add_edge(0, 3).unwrap();
226 g.add_edge(0, 4).unwrap();
227 assert!(!is_fork_free(&g).unwrap());
228 }
229
230 #[test]
231 fn directed_returns_false() {
232 let mut g = Graph::new(3, true).unwrap();
233 g.add_edge(0, 1).unwrap();
234 g.add_edge(1, 2).unwrap();
235 g.add_edge(2, 0).unwrap();
236 assert!(!is_fork_free(&g).unwrap());
237 }
238
239 #[test]
240 fn pendant_adjacent_to_another_arm() {
241 let mut g = Graph::with_vertices(5);
244 g.add_edge(0, 1).unwrap();
245 g.add_edge(0, 2).unwrap();
246 g.add_edge(0, 3).unwrap();
247 g.add_edge(3, 4).unwrap();
248 g.add_edge(1, 4).unwrap();
249 assert!(is_fork_free(&g).unwrap());
250 }
251
252 #[test]
253 fn c5_fork_free() {
254 let mut g = Graph::with_vertices(5);
256 g.add_edge(0, 1).unwrap();
257 g.add_edge(1, 2).unwrap();
258 g.add_edge(2, 3).unwrap();
259 g.add_edge(3, 4).unwrap();
260 g.add_edge(4, 0).unwrap();
261 assert!(is_fork_free(&g).unwrap());
262 }
263
264 #[test]
265 fn star_s5_is_fork_free() {
266 let mut g = Graph::with_vertices(6);
270 g.add_edge(0, 1).unwrap();
271 g.add_edge(0, 2).unwrap();
272 g.add_edge(0, 3).unwrap();
273 g.add_edge(0, 4).unwrap();
274 g.add_edge(0, 5).unwrap();
275 assert!(is_fork_free(&g).unwrap());
276 }
277}