rust_igraph/algorithms/properties/
is_triangle_free.rs1use crate::core::{Graph, IgraphResult};
9
10pub fn is_triangle_free(graph: &Graph) -> IgraphResult<bool> {
35 let n = graph.vcount() as usize;
36 let m = graph.ecount();
37
38 if n < 3 || m < 3 {
40 return Ok(true);
41 }
42
43 let adj = build_sorted_simple_adjlist(graph)?;
45
46 for u in 0..n {
49 let du = adj[u].len();
50 if du < 2 {
51 continue;
52 }
53 for &v in &adj[u] {
54 if v <= u {
55 continue;
56 }
57 let dv = adj[v].len();
58 if dv < 2 {
59 continue;
60 }
61
62 if has_common_element(&adj[u], &adj[v]) {
64 return Ok(false);
65 }
66 }
67 }
68
69 Ok(true)
70}
71
72fn has_common_element(a: &[usize], b: &[usize]) -> bool {
74 let mut i = 0;
75 let mut j = 0;
76 while i < a.len() && j < b.len() {
77 match a[i].cmp(&b[j]) {
78 std::cmp::Ordering::Equal => return true,
79 std::cmp::Ordering::Less => i += 1,
80 std::cmp::Ordering::Greater => j += 1,
81 }
82 }
83 false
84}
85
86fn build_sorted_simple_adjlist(graph: &Graph) -> IgraphResult<Vec<Vec<usize>>> {
88 let n = graph.vcount() as usize;
89 let m = graph.ecount();
90 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
91
92 for eid_usize in 0..m {
93 #[allow(clippy::cast_possible_truncation)]
94 let eid = eid_usize as u32;
95 let (from, to) = graph.edge(eid)?;
96 let f = from as usize;
97 let t = to as usize;
98 if f != t {
99 adj[f].push(t);
100 adj[t].push(f);
101 }
102 }
103
104 for neighbors in &mut adj {
105 neighbors.sort_unstable();
106 neighbors.dedup();
107 }
108
109 Ok(adj)
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn empty_graph() {
118 let g = Graph::with_vertices(0);
119 assert!(is_triangle_free(&g).unwrap());
120 }
121
122 #[test]
123 fn single_vertex() {
124 let g = Graph::with_vertices(1);
125 assert!(is_triangle_free(&g).unwrap());
126 }
127
128 #[test]
129 fn single_edge() {
130 let mut g = Graph::with_vertices(2);
131 g.add_edge(0, 1).unwrap();
132 assert!(is_triangle_free(&g).unwrap());
133 }
134
135 #[test]
136 fn triangle() {
137 let mut g = Graph::with_vertices(3);
138 g.add_edge(0, 1).unwrap();
139 g.add_edge(1, 2).unwrap();
140 g.add_edge(2, 0).unwrap();
141 assert!(!is_triangle_free(&g).unwrap());
142 }
143
144 #[test]
145 fn path_is_triangle_free() {
146 let mut g = Graph::with_vertices(5);
147 g.add_edge(0, 1).unwrap();
148 g.add_edge(1, 2).unwrap();
149 g.add_edge(2, 3).unwrap();
150 g.add_edge(3, 4).unwrap();
151 assert!(is_triangle_free(&g).unwrap());
152 }
153
154 #[test]
155 fn cycle_4_is_triangle_free() {
156 let mut g = Graph::with_vertices(4);
157 g.add_edge(0, 1).unwrap();
158 g.add_edge(1, 2).unwrap();
159 g.add_edge(2, 3).unwrap();
160 g.add_edge(3, 0).unwrap();
161 assert!(is_triangle_free(&g).unwrap());
162 }
163
164 #[test]
165 fn cycle_5_is_triangle_free() {
166 let mut g = Graph::with_vertices(5);
167 g.add_edge(0, 1).unwrap();
168 g.add_edge(1, 2).unwrap();
169 g.add_edge(2, 3).unwrap();
170 g.add_edge(3, 4).unwrap();
171 g.add_edge(4, 0).unwrap();
172 assert!(is_triangle_free(&g).unwrap());
173 }
174
175 #[test]
176 fn complete_4_not_triangle_free() {
177 let mut g = Graph::with_vertices(4);
178 for i in 0..4u32 {
179 for j in (i + 1)..4 {
180 g.add_edge(i, j).unwrap();
181 }
182 }
183 assert!(!is_triangle_free(&g).unwrap());
184 }
185
186 #[test]
187 fn bipartite_is_triangle_free() {
188 let mut g = Graph::with_vertices(6);
190 for i in 0..3u32 {
191 for j in 3..6u32 {
192 g.add_edge(i, j).unwrap();
193 }
194 }
195 assert!(is_triangle_free(&g).unwrap());
196 }
197
198 #[test]
199 fn self_loops_ignored() {
200 let mut g = Graph::with_vertices(3);
201 g.add_edge(0, 0).unwrap();
202 g.add_edge(1, 1).unwrap();
203 g.add_edge(0, 1).unwrap();
204 assert!(is_triangle_free(&g).unwrap());
205 }
206
207 #[test]
208 fn multi_edges_ignored() {
209 let mut g = Graph::with_vertices(3);
210 g.add_edge(0, 1).unwrap();
211 g.add_edge(0, 1).unwrap();
212 g.add_edge(1, 2).unwrap();
213 assert!(is_triangle_free(&g).unwrap());
215 }
216
217 #[test]
218 fn directed_ignores_direction() {
219 let mut g = Graph::new(3, true).unwrap();
220 g.add_edge(0, 1).unwrap();
221 g.add_edge(1, 2).unwrap();
222 g.add_edge(2, 0).unwrap();
223 assert!(!is_triangle_free(&g).unwrap());
225 }
226
227 #[test]
228 fn no_edges() {
229 let g = Graph::with_vertices(10);
230 assert!(is_triangle_free(&g).unwrap());
231 }
232
233 #[test]
234 fn star_is_triangle_free() {
235 let mut g = Graph::with_vertices(5);
236 g.add_edge(0, 1).unwrap();
237 g.add_edge(0, 2).unwrap();
238 g.add_edge(0, 3).unwrap();
239 g.add_edge(0, 4).unwrap();
240 assert!(is_triangle_free(&g).unwrap());
241 }
242
243 #[test]
244 fn petersen_is_triangle_free() {
245 let mut g = Graph::with_vertices(10);
247 g.add_edge(0, 1).unwrap();
249 g.add_edge(1, 2).unwrap();
250 g.add_edge(2, 3).unwrap();
251 g.add_edge(3, 4).unwrap();
252 g.add_edge(4, 0).unwrap();
253 g.add_edge(5, 7).unwrap();
255 g.add_edge(7, 9).unwrap();
256 g.add_edge(9, 6).unwrap();
257 g.add_edge(6, 8).unwrap();
258 g.add_edge(8, 5).unwrap();
259 g.add_edge(0, 5).unwrap();
261 g.add_edge(1, 6).unwrap();
262 g.add_edge(2, 7).unwrap();
263 g.add_edge(3, 8).unwrap();
264 g.add_edge(4, 9).unwrap();
265
266 assert!(is_triangle_free(&g).unwrap());
267 }
268}