1#![allow(
16 clippy::cast_possible_truncation,
17 clippy::cast_precision_loss,
18 clippy::many_single_char_names,
19 clippy::needless_range_loop,
20 clippy::too_many_lines
21)]
22
23use crate::core::{Graph, IgraphError, IgraphResult};
24
25pub fn greedy_coloring(graph: &Graph) -> IgraphResult<Vec<u32>> {
45 greedy_coloring_with_order(graph, None)
46}
47
48pub fn greedy_coloring_with_order(graph: &Graph, order: Option<&[u32]>) -> IgraphResult<Vec<u32>> {
65 let n = graph.vcount() as usize;
66 if n == 0 {
67 return Ok(Vec::new());
68 }
69
70 if let Some(ord) = order {
71 if ord.len() != n {
72 return Err(IgraphError::InvalidArgument(
73 "greedy_coloring_with_order: order length must equal vertex count".into(),
74 ));
75 }
76 }
77
78 let mut colors = vec![u32::MAX; n];
79 let mut used = vec![false; n + 1];
80
81 for idx in 0..n {
82 let v = if let Some(ord) = order {
83 ord[idx] as usize
84 } else {
85 idx
86 };
87
88 let nbrs = neighbors_both(graph, v as u32)?;
89 for &u in &nbrs {
90 let ui = u as usize;
91 if colors[ui] != u32::MAX {
92 let c = colors[ui] as usize;
93 if c < used.len() {
94 used[c] = true;
95 }
96 }
97 }
98
99 let mut c = 0_u32;
100 while (c as usize) < used.len() && used[c as usize] {
101 c = c.saturating_add(1);
102 }
103 colors[v] = c;
104
105 for &u in &nbrs {
106 let ui = u as usize;
107 if colors[ui] != u32::MAX {
108 let cu = colors[ui] as usize;
109 if cu < used.len() {
110 used[cu] = false;
111 }
112 }
113 }
114 }
115
116 Ok(colors)
117}
118
119pub fn greedy_coloring_largest_first(graph: &Graph) -> IgraphResult<Vec<u32>> {
138 let n = graph.vcount() as usize;
139 if n == 0 {
140 return Ok(Vec::new());
141 }
142
143 let mut deg_order: Vec<(usize, u32)> = (0..n)
144 .map(|v| {
145 let d = degree_both(graph, v as u32);
146 (d, v as u32)
147 })
148 .collect();
149 deg_order.sort_by(|a, b| b.0.cmp(&a.0).then(a.1.cmp(&b.1)));
150
151 let order: Vec<u32> = deg_order.iter().map(|&(_, v)| v).collect();
152 greedy_coloring_with_order(graph, Some(&order))
153}
154
155pub fn chromatic_number_greedy(coloring: &[u32]) -> u32 {
167 if coloring.is_empty() {
168 return 0;
169 }
170 coloring.iter().max().map_or(0, |&m| m + 1)
171}
172
173pub fn is_proper_coloring(graph: &Graph, coloring: &[u32]) -> IgraphResult<bool> {
188 let n = graph.vcount() as usize;
189 if coloring.len() != n {
190 return Err(IgraphError::InvalidArgument(
191 "is_proper_coloring: coloring length must equal vertex count".into(),
192 ));
193 }
194
195 for (u, v) in graph.edges() {
196 let ui = u as usize;
197 let vi = v as usize;
198 if coloring[ui] == coloring[vi] {
199 return Ok(false);
200 }
201 }
202 Ok(true)
203}
204
205pub fn greedy_clique_number(graph: &Graph) -> IgraphResult<u32> {
223 if graph.is_directed() {
224 return Err(IgraphError::InvalidArgument(
225 "greedy_clique_number is defined for undirected graphs only".into(),
226 ));
227 }
228
229 let n = graph.vcount() as usize;
230 if n == 0 {
231 return Ok(0);
232 }
233
234 let adj = build_adj_set(graph);
235
236 let mut best_clique = 0_u32;
237 let mut used = vec![false; n];
238
239 for _ in 0..n {
240 let mut clique: Vec<u32> = Vec::new();
241 let mut candidates: Vec<u32> = (0..n as u32).filter(|&v| !used[v as usize]).collect();
242
243 while !candidates.is_empty() {
244 let mut scored: Vec<(usize, u32)> = candidates
245 .iter()
246 .map(|&v| {
247 let d = candidates
248 .iter()
249 .filter(|&&c| c != v && is_adj(&adj, n, v, c))
250 .count();
251 (d, v)
252 })
253 .collect();
254 scored.sort_by(|a, b| b.0.cmp(&a.0).then(a.1.cmp(&b.1)));
255
256 let v = scored[0].1;
257 clique.push(v);
258 candidates.retain(|&c| c != v && is_adj(&adj, n, v, c));
259 }
260
261 if clique.len() as u32 > best_clique {
262 best_clique = clique.len() as u32;
263 }
264
265 if let Some(&first) = clique.first() {
266 used[first as usize] = true;
267 } else {
268 break;
269 }
270 }
271
272 Ok(best_clique)
273}
274
275fn build_adj_set(graph: &Graph) -> Vec<bool> {
276 let n = graph.vcount() as usize;
277 let mut adj = vec![false; n * n];
278 for (u, v) in graph.edges() {
279 let ui = u as usize;
280 let vi = v as usize;
281 adj[ui * n + vi] = true;
282 adj[vi * n + ui] = true;
283 }
284 adj
285}
286
287fn is_adj(adj: &[bool], n: usize, u: u32, v: u32) -> bool {
288 adj[u as usize * n + v as usize]
289}
290
291fn neighbors_both(graph: &Graph, v: u32) -> IgraphResult<Vec<u32>> {
292 if graph.is_directed() {
293 let mut nbrs = graph.neighbors(v)?;
294 if let Ok(in_nbrs) = graph.in_neighbors_vec(v) {
295 for u in in_nbrs {
296 if !nbrs.contains(&u) {
297 nbrs.push(u);
298 }
299 }
300 }
301 Ok(nbrs)
302 } else {
303 graph.neighbors(v)
304 }
305}
306
307fn degree_both(graph: &Graph, v: u32) -> usize {
308 neighbors_both(graph, v).map_or(0, |n| n.len())
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 fn path3() -> Graph {
316 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
317 }
318
319 fn k3() -> Graph {
320 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
321 }
322
323 fn k4() -> Graph {
324 Graph::from_edges(
325 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
326 false,
327 Some(4),
328 )
329 .unwrap()
330 }
331
332 fn cycle5() -> Graph {
333 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
334 }
335
336 fn star4() -> Graph {
337 Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false, Some(4)).unwrap()
338 }
339
340 fn petersen() -> Graph {
341 Graph::from_edges(
342 &[
343 (0, 1),
344 (1, 2),
345 (2, 3),
346 (3, 4),
347 (4, 0),
348 (0, 5),
349 (1, 6),
350 (2, 7),
351 (3, 8),
352 (4, 9),
353 (5, 7),
354 (7, 9),
355 (9, 6),
356 (6, 8),
357 (8, 5),
358 ],
359 false,
360 Some(10),
361 )
362 .unwrap()
363 }
364
365 fn bipartite_k23() -> Graph {
366 Graph::from_edges(
367 &[(0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4)],
368 false,
369 Some(5),
370 )
371 .unwrap()
372 }
373
374 #[test]
377 fn gc_empty() {
378 let g = Graph::with_vertices(0);
379 let c = greedy_coloring(&g).unwrap();
380 assert!(c.is_empty());
381 }
382
383 #[test]
384 fn gc_single() {
385 let g = Graph::with_vertices(1);
386 let c = greedy_coloring(&g).unwrap();
387 assert_eq!(c, vec![0]);
388 }
389
390 #[test]
391 fn gc_isolated() {
392 let g = Graph::with_vertices(5);
393 let c = greedy_coloring(&g).unwrap();
394 assert_eq!(c, vec![0, 0, 0, 0, 0]);
395 }
396
397 #[test]
398 fn gc_path3() {
399 let g = path3();
400 let c = greedy_coloring(&g).unwrap();
401 assert_eq!(c, vec![0, 1, 0]);
402 assert!(is_proper_coloring(&g, &c).unwrap());
403 }
404
405 #[test]
406 fn gc_k3() {
407 let g = k3();
408 let c = greedy_coloring(&g).unwrap();
409 assert_eq!(chromatic_number_greedy(&c), 3);
410 assert!(is_proper_coloring(&g, &c).unwrap());
411 }
412
413 #[test]
414 fn gc_k4() {
415 let g = k4();
416 let c = greedy_coloring(&g).unwrap();
417 assert_eq!(chromatic_number_greedy(&c), 4);
418 assert!(is_proper_coloring(&g, &c).unwrap());
419 }
420
421 #[test]
422 fn gc_bipartite() {
423 let g = bipartite_k23();
424 let c = greedy_coloring(&g).unwrap();
425 assert!(chromatic_number_greedy(&c) <= 3);
426 assert!(is_proper_coloring(&g, &c).unwrap());
427 }
428
429 #[test]
430 fn gc_star() {
431 let g = star4();
432 let c = greedy_coloring(&g).unwrap();
433 assert_eq!(chromatic_number_greedy(&c), 2);
434 assert!(is_proper_coloring(&g, &c).unwrap());
435 }
436
437 #[test]
440 fn gclf_k3() {
441 let g = k3();
442 let c = greedy_coloring_largest_first(&g).unwrap();
443 assert_eq!(chromatic_number_greedy(&c), 3);
444 assert!(is_proper_coloring(&g, &c).unwrap());
445 }
446
447 #[test]
448 fn gclf_cycle5() {
449 let g = cycle5();
450 let c = greedy_coloring_largest_first(&g).unwrap();
451 assert!(chromatic_number_greedy(&c) <= 3);
452 assert!(is_proper_coloring(&g, &c).unwrap());
453 }
454
455 #[test]
456 fn gclf_bipartite() {
457 let g = bipartite_k23();
458 let c = greedy_coloring_largest_first(&g).unwrap();
459 assert_eq!(chromatic_number_greedy(&c), 2);
460 assert!(is_proper_coloring(&g, &c).unwrap());
461 }
462
463 #[test]
464 fn gclf_petersen() {
465 let g = petersen();
466 let c = greedy_coloring_largest_first(&g).unwrap();
467 assert!(chromatic_number_greedy(&c) <= 4);
469 assert!(is_proper_coloring(&g, &c).unwrap());
470 }
471
472 #[test]
475 fn gcwo_custom_order() {
476 let g = path3();
477 let c = greedy_coloring_with_order(&g, Some(&[2, 0, 1])).unwrap();
478 assert!(is_proper_coloring(&g, &c).unwrap());
479 assert_eq!(chromatic_number_greedy(&c), 2);
480 }
481
482 #[test]
483 fn gcwo_wrong_length() {
484 let g = path3();
485 assert!(greedy_coloring_with_order(&g, Some(&[0, 1])).is_err());
486 }
487
488 #[test]
491 fn cng_empty() {
492 assert_eq!(chromatic_number_greedy(&[]), 0);
493 }
494
495 #[test]
496 fn cng_single() {
497 assert_eq!(chromatic_number_greedy(&[0]), 1);
498 }
499
500 #[test]
503 fn ipc_valid() {
504 let g = k3();
505 assert!(is_proper_coloring(&g, &[0, 1, 2]).unwrap());
506 }
507
508 #[test]
509 fn ipc_invalid() {
510 let g = k3();
511 assert!(!is_proper_coloring(&g, &[0, 1, 0]).unwrap());
512 }
513
514 #[test]
515 fn ipc_wrong_length() {
516 let g = k3();
517 assert!(is_proper_coloring(&g, &[0, 1]).is_err());
518 }
519
520 #[test]
523 fn gcn_empty() {
524 let g = Graph::with_vertices(0);
525 assert_eq!(greedy_clique_number(&g).unwrap(), 0);
526 }
527
528 #[test]
529 fn gcn_single() {
530 let g = Graph::with_vertices(1);
531 assert_eq!(greedy_clique_number(&g).unwrap(), 1);
532 }
533
534 #[test]
535 fn gcn_k4() {
536 let g = k4();
537 assert_eq!(greedy_clique_number(&g).unwrap(), 4);
538 }
539
540 #[test]
541 fn gcn_cycle5() {
542 let g = cycle5();
543 let cn = greedy_clique_number(&g).unwrap();
544 assert_eq!(cn, 2);
545 }
546
547 #[test]
548 fn gcn_petersen() {
549 let g = petersen();
550 let cn = greedy_clique_number(&g).unwrap();
551 assert_eq!(cn, 2);
552 }
553
554 #[test]
555 fn gcn_directed_error() {
556 let g = Graph::from_edges(&[(0, 1)], true, Some(2)).unwrap();
557 assert!(greedy_clique_number(&g).is_err());
558 }
559
560 #[test]
563 fn coloring_always_proper() {
564 for g in &[path3(), k3(), k4(), cycle5(), star4(), petersen()] {
565 let c = greedy_coloring(g).unwrap();
566 assert!(is_proper_coloring(g, &c).unwrap());
567 }
568 }
569
570 #[test]
571 fn lf_never_worse_than_max_degree_plus_one() {
572 for g in &[path3(), k3(), k4(), cycle5(), star4(), petersen()] {
573 let c = greedy_coloring_largest_first(g).unwrap();
574 let num_colors = chromatic_number_greedy(&c);
575 let max_deg = (0..g.vcount())
576 .map(|v| degree_both(g, v) as u32)
577 .max()
578 .unwrap_or(0);
579 assert!(
580 num_colors <= max_deg + 1,
581 "greedy LF used {num_colors} colors but Δ+1 = {}",
582 max_deg + 1
583 );
584 }
585 }
586
587 #[test]
588 fn clique_bound_vs_coloring() {
589 for g in &[k3(), k4(), cycle5(), petersen()] {
590 let cn = greedy_clique_number(g).unwrap();
591 let c = greedy_coloring_largest_first(g).unwrap();
592 let chi = chromatic_number_greedy(&c);
593 assert!(cn <= chi, "clique number {cn} > chromatic bound {chi}");
594 }
595 }
596
597 #[test]
600 fn gc_directed() {
601 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], true, Some(3)).unwrap();
602 let c = greedy_coloring(&g).unwrap();
603 assert_eq!(chromatic_number_greedy(&c), 3);
604 assert!(is_proper_coloring(&g, &c).unwrap());
605 }
606}