rust_igraph/algorithms/properties/
matching.rs1#![allow(
14 clippy::cast_possible_truncation,
15 clippy::cast_precision_loss,
16 clippy::many_single_char_names,
17 clippy::needless_range_loop,
18 clippy::too_many_lines
19)]
20
21use crate::core::{Graph, IgraphError, IgraphResult};
22
23pub fn greedy_matching(graph: &Graph) -> IgraphResult<Vec<usize>> {
41 let n = graph.vcount() as usize;
42 let mut matched = vec![false; n];
43 let mut matching = Vec::new();
44
45 for (eidx, (u, v)) in graph.edges().enumerate() {
46 let ui = u as usize;
47 let vi = v as usize;
48 if !matched[ui] && !matched[vi] {
49 matching.push(eidx);
50 matched[ui] = true;
51 matched[vi] = true;
52 }
53 }
54
55 Ok(matching)
56}
57
58pub fn maximum_matching(graph: &Graph) -> IgraphResult<Vec<usize>> {
74 let edges: Vec<(u32, u32)> = graph.edges().collect();
75 let m = edges.len();
76 let n = graph.vcount() as usize;
77
78 if m == 0 || n < 2 {
79 return Ok(Vec::new());
80 }
81
82 let mut best: Vec<usize> = Vec::new();
83 max_matching_branch(
84 &edges,
85 n,
86 0,
87 &mut Vec::new(),
88 &mut vec![false; n],
89 &mut best,
90 );
91
92 Ok(best)
93}
94
95fn max_matching_branch(
96 edges: &[(u32, u32)],
97 n: usize,
98 start: usize,
99 current: &mut Vec<usize>,
100 matched: &mut Vec<bool>,
101 best: &mut Vec<usize>,
102) {
103 if current.len() > best.len() {
104 *best = current.clone();
105 }
106
107 let remaining_possible = (edges.len() - start).min(n / 2);
108 if current.len() + remaining_possible <= best.len() {
109 return;
110 }
111
112 for i in start..edges.len() {
113 let (u, v) = edges[i];
114 let ui = u as usize;
115 let vi = v as usize;
116 if !matched[ui] && !matched[vi] {
117 matched[ui] = true;
118 matched[vi] = true;
119 current.push(i);
120 max_matching_branch(edges, n, i + 1, current, matched, best);
121 current.pop();
122 matched[ui] = false;
123 matched[vi] = false;
124 }
125 }
126}
127
128pub fn matching_number(graph: &Graph) -> IgraphResult<u32> {
145 let m = maximum_matching(graph)?;
146 Ok(m.len() as u32)
147}
148
149pub fn is_valid_matching(graph: &Graph, edge_indices: &[usize]) -> IgraphResult<bool> {
163 let n = graph.vcount() as usize;
164 let edges: Vec<(u32, u32)> = graph.edges().collect();
165 let m = edges.len();
166
167 let mut used = vec![false; n];
168
169 for &eidx in edge_indices {
170 if eidx >= m {
171 return Err(IgraphError::InvalidArgument(format!(
172 "is_valid_matching: edge index {eidx} out of range (ecount={m})"
173 )));
174 }
175 let (u, v) = edges[eidx];
176 let ui = u as usize;
177 let vi = v as usize;
178 if used[ui] || used[vi] {
179 return Ok(false);
180 }
181 used[ui] = true;
182 used[vi] = true;
183 }
184
185 Ok(true)
186}
187
188pub fn is_perfect_matching(graph: &Graph, edge_indices: &[usize]) -> IgraphResult<bool> {
206 let n = graph.vcount() as usize;
207 if n % 2 != 0 {
208 return Ok(false);
209 }
210 if edge_indices.len() != n / 2 {
211 return Ok(false);
212 }
213 is_valid_matching(graph, edge_indices)
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 fn path4() -> Graph {
221 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
222 }
223
224 fn k3() -> Graph {
225 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
226 }
227
228 fn k4() -> Graph {
229 Graph::from_edges(
230 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
231 false,
232 Some(4),
233 )
234 .unwrap()
235 }
236
237 fn cycle4() -> Graph {
238 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
239 }
240
241 fn cycle5() -> Graph {
242 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
243 }
244
245 fn star5() -> Graph {
246 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
247 }
248
249 fn petersen() -> Graph {
250 Graph::from_edges(
251 &[
252 (0, 1),
253 (1, 2),
254 (2, 3),
255 (3, 4),
256 (4, 0),
257 (0, 5),
258 (1, 6),
259 (2, 7),
260 (3, 8),
261 (4, 9),
262 (5, 7),
263 (7, 9),
264 (9, 6),
265 (6, 8),
266 (8, 5),
267 ],
268 false,
269 Some(10),
270 )
271 .unwrap()
272 }
273
274 #[test]
277 fn gm_empty() {
278 let g = Graph::with_vertices(0);
279 assert!(greedy_matching(&g).unwrap().is_empty());
280 }
281
282 #[test]
283 fn gm_single() {
284 let g = Graph::with_vertices(1);
285 assert!(greedy_matching(&g).unwrap().is_empty());
286 }
287
288 #[test]
289 fn gm_path4() {
290 let g = path4();
291 let m = greedy_matching(&g).unwrap();
292 assert!(is_valid_matching(&g, &m).unwrap());
293 assert!(!m.is_empty());
294 }
295
296 #[test]
297 fn gm_k4() {
298 let g = k4();
299 let m = greedy_matching(&g).unwrap();
300 assert!(is_valid_matching(&g, &m).unwrap());
301 }
302
303 #[test]
306 fn mm_empty() {
307 let g = Graph::with_vertices(0);
308 assert!(maximum_matching(&g).unwrap().is_empty());
309 }
310
311 #[test]
312 fn mm_single_edge() {
313 let g = Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap();
314 let m = maximum_matching(&g).unwrap();
315 assert_eq!(m.len(), 1);
316 }
317
318 #[test]
319 fn mm_path4() {
320 let g = path4();
321 let m = maximum_matching(&g).unwrap();
322 assert_eq!(m.len(), 2);
323 assert!(is_valid_matching(&g, &m).unwrap());
324 }
325
326 #[test]
327 fn mm_k3() {
328 let g = k3();
329 let m = maximum_matching(&g).unwrap();
330 assert_eq!(m.len(), 1);
331 }
332
333 #[test]
334 fn mm_k4() {
335 let g = k4();
336 let m = maximum_matching(&g).unwrap();
337 assert_eq!(m.len(), 2);
338 assert!(is_perfect_matching(&g, &m).unwrap());
339 }
340
341 #[test]
342 fn mm_cycle4() {
343 let g = cycle4();
344 let m = maximum_matching(&g).unwrap();
345 assert_eq!(m.len(), 2);
346 assert!(is_perfect_matching(&g, &m).unwrap());
347 }
348
349 #[test]
350 fn mm_cycle5() {
351 let g = cycle5();
352 let m = maximum_matching(&g).unwrap();
353 assert_eq!(m.len(), 2);
354 }
355
356 #[test]
357 fn mm_star5() {
358 let g = star5();
359 let m = maximum_matching(&g).unwrap();
360 assert_eq!(m.len(), 1);
361 }
362
363 #[test]
364 fn mm_petersen() {
365 let g = petersen();
366 let m = maximum_matching(&g).unwrap();
367 assert_eq!(m.len(), 5);
368 assert!(is_perfect_matching(&g, &m).unwrap());
369 }
370
371 #[test]
372 fn mm_isolated() {
373 let g = Graph::with_vertices(4);
374 let m = maximum_matching(&g).unwrap();
375 assert!(m.is_empty());
376 }
377
378 #[test]
381 fn mn_k4() {
382 assert_eq!(matching_number(&k4()).unwrap(), 2);
383 }
384
385 #[test]
386 fn mn_path4() {
387 assert_eq!(matching_number(&path4()).unwrap(), 2);
388 }
389
390 #[test]
391 fn mn_star5() {
392 assert_eq!(matching_number(&star5()).unwrap(), 1);
393 }
394
395 #[test]
398 fn ivm_valid() {
399 let g = path4();
400 assert!(is_valid_matching(&g, &[0, 2]).unwrap());
401 }
402
403 #[test]
404 fn ivm_invalid() {
405 let g = path4();
406 assert!(!is_valid_matching(&g, &[0, 1]).unwrap());
407 }
408
409 #[test]
410 fn ivm_empty() {
411 let g = path4();
412 assert!(is_valid_matching(&g, &[]).unwrap());
413 }
414
415 #[test]
416 fn ivm_out_of_range() {
417 let g = path4();
418 assert!(is_valid_matching(&g, &[10]).is_err());
419 }
420
421 #[test]
424 fn ipm_k4_perfect() {
425 let g = k4();
426 let m = maximum_matching(&g).unwrap();
427 assert!(is_perfect_matching(&g, &m).unwrap());
428 }
429
430 #[test]
431 fn ipm_odd_vertices() {
432 let g = k3();
433 let m = maximum_matching(&g).unwrap();
434 assert!(!is_perfect_matching(&g, &m).unwrap());
435 }
436
437 #[test]
438 fn ipm_not_enough_edges() {
439 let g = k4();
440 assert!(!is_perfect_matching(&g, &[0]).unwrap());
441 }
442
443 #[test]
446 fn greedy_is_valid() {
447 for g in &[path4(), k3(), k4(), cycle4(), cycle5(), star5(), petersen()] {
448 let m = greedy_matching(g).unwrap();
449 assert!(is_valid_matching(g, &m).unwrap());
450 }
451 }
452
453 #[test]
454 fn greedy_at_most_maximum() {
455 for g in &[path4(), k3(), k4(), cycle5(), star5()] {
456 let gm = greedy_matching(g).unwrap();
457 let mm = maximum_matching(g).unwrap();
458 assert!(gm.len() <= mm.len());
459 }
460 }
461
462 #[test]
463 fn matching_number_bounded() {
464 for g in &[path4(), k3(), k4(), cycle5(), star5()] {
465 let nu = matching_number(g).unwrap();
466 let n = g.vcount();
467 assert!(nu <= n / 2);
468 }
469 }
470
471 #[test]
472 fn gallai_identity() {
473 let g = path4(); let alpha = crate::algorithms::cliques::independence_number(&g).unwrap();
481 let nu = matching_number(&g).unwrap();
482 let n = g.vcount();
483 assert_eq!(alpha + nu, n);
484 }
485}