use crate::error::OptimizeError;
pub type ColoringResult<T> = Result<T, OptimizeError>;
#[derive(Debug, Clone)]
pub struct GraphColoring {
pub adj: Vec<Vec<usize>>,
pub n: usize,
}
impl GraphColoring {
pub fn new(n: usize) -> Self {
Self {
adj: vec![vec![]; n],
n,
}
}
pub fn add_edge(&mut self, u: usize, v: usize) -> ColoringResult<()> {
if u >= self.n || v >= self.n {
return Err(OptimizeError::InvalidInput(format!(
"Edge ({u},{v}) out of range for graph with {} vertices",
self.n
)));
}
if u == v {
return Ok(()); }
if !self.adj[u].contains(&v) {
self.adj[u].push(v);
}
if !self.adj[v].contains(&u) {
self.adj[v].push(u);
}
Ok(())
}
pub fn degree(&self, v: usize) -> usize {
self.adj[v].len()
}
pub fn greedy_coloring(&self) -> Vec<usize> {
if self.n == 0 {
return vec![];
}
let mut order: Vec<usize> = (0..self.n).collect();
order.sort_by(|&a, &b| self.degree(b).cmp(&self.degree(a)));
let mut color = vec![usize::MAX; self.n];
for &v in &order {
let mut neighbor_colors = std::collections::HashSet::new();
for &u in &self.adj[v] {
if color[u] != usize::MAX {
neighbor_colors.insert(color[u]);
}
}
let mut c = 0;
while neighbor_colors.contains(&c) {
c += 1;
}
color[v] = c;
}
color
}
pub fn dsatur_coloring(&self) -> Vec<usize> {
if self.n == 0 {
return vec![];
}
let mut color = vec![usize::MAX; self.n];
let mut saturation = vec![0usize; self.n];
let mut neighbor_colors: Vec<std::collections::HashSet<usize>> =
vec![std::collections::HashSet::new(); self.n];
let mut colored = 0usize;
while colored < self.n {
let v = match (0..self.n)
.filter(|&u| color[u] == usize::MAX)
.max_by(|&a, &b| {
saturation[a]
.cmp(&saturation[b])
.then(self.degree(a).cmp(&self.degree(b)))
})
{
Some(vertex) => vertex,
None => break, };
let mut c = 0;
while neighbor_colors[v].contains(&c) {
c += 1;
}
color[v] = c;
colored += 1;
for &u in &self.adj[v] {
if color[u] == usize::MAX && !neighbor_colors[u].contains(&c) {
neighbor_colors[u].insert(c);
saturation[u] += 1;
}
}
}
color
}
pub fn backtrack_coloring(&self, k: usize) -> Option<Vec<usize>> {
if self.n == 0 {
return Some(vec![]);
}
if k == 0 {
return None;
}
let mut color = vec![k; self.n]; let mut order: Vec<usize> = (0..self.n).collect();
order.sort_by(|&a, &b| self.degree(b).cmp(&self.degree(a)));
let pos_of: Vec<usize> = {
let mut p = vec![0usize; self.n];
for (pos, &v) in order.iter().enumerate() {
p[v] = pos;
}
p
};
if self.backtrack_inner(&mut color, &order, &pos_of, k, 0) {
Some(color)
} else {
None
}
}
fn backtrack_inner(
&self,
color: &mut Vec<usize>,
order: &[usize],
pos_of: &[usize],
k: usize,
idx: usize,
) -> bool {
if idx == self.n {
return true;
}
let v = order[idx];
let mut forbidden = vec![false; k];
for &u in &self.adj[v] {
if color[u] < k {
forbidden[color[u]] = true;
}
}
for c in 0..k {
if forbidden[c] {
continue;
}
let ok = self.adj[v].iter().all(|&u| {
if color[u] < k {
return true; }
let used: std::collections::HashSet<usize> = self.adj[u]
.iter()
.filter(|&&w| color[w] < k && w != v)
.map(|&w| color[w])
.collect();
let available = (0..k).filter(|&x| x != c && !used.contains(&x)).count();
if pos_of[u] > idx {
available > 0
} else {
true
}
});
if !ok {
continue;
}
color[v] = c;
if self.backtrack_inner(color, order, pos_of, k, idx + 1) {
return true;
}
color[v] = k; }
false
}
pub fn chromatic_number(&self) -> usize {
if self.n == 0 {
return 0;
}
let has_edges = self.adj.iter().any(|nbrs| !nbrs.is_empty());
let lower = if has_edges { 2 } else { 1 };
let upper_coloring = self.dsatur_coloring();
let upper = upper_coloring.iter().cloned().max().map(|m| m + 1).unwrap_or(1);
if lower >= upper {
return lower;
}
let mut lo = lower;
let mut hi = upper;
while lo < hi {
let mid = (lo + hi) / 2;
if self.backtrack_coloring(mid).is_some() {
hi = mid;
} else {
lo = mid + 1;
}
}
lo
}
pub fn is_valid_coloring(&self, coloring: &[usize]) -> bool {
if coloring.len() != self.n {
return false;
}
for u in 0..self.n {
for &v in &self.adj[u] {
if coloring[u] == coloring[v] {
return false;
}
}
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cycle_graph(n: usize) -> GraphColoring {
let mut g = GraphColoring::new(n);
for i in 0..n {
g.add_edge(i, (i + 1) % n).expect("unexpected None or Err");
}
g
}
fn complete_graph(n: usize) -> GraphColoring {
let mut g = GraphColoring::new(n);
for i in 0..n {
for j in i + 1..n {
g.add_edge(i, j).expect("unexpected None or Err");
}
}
g
}
#[test]
fn test_greedy_valid() {
let g = cycle_graph(6);
let c = g.greedy_coloring();
assert!(g.is_valid_coloring(&c));
}
#[test]
fn test_dsatur_valid() {
let g = complete_graph(5);
let c = g.dsatur_coloring();
assert!(g.is_valid_coloring(&c));
let num_colors = c.iter().cloned().max().expect("failed to create num_colors") + 1;
assert_eq!(num_colors, 5);
}
#[test]
fn test_bipartite_2_colorable() {
let mut g = GraphColoring::new(6);
for u in 0..3 {
for v in 3..6 {
g.add_edge(u, v).expect("unexpected None or Err");
}
}
let c = g.backtrack_coloring(2);
assert!(c.is_some());
assert!(g.is_valid_coloring(c.as_ref().expect("unexpected None or Err")));
}
#[test]
fn test_odd_cycle_not_2_colorable() {
let g = cycle_graph(5); assert!(g.backtrack_coloring(2).is_none());
assert!(g.backtrack_coloring(3).is_some());
}
#[test]
fn test_chromatic_number_cycle() {
let even = cycle_graph(6);
assert_eq!(even.chromatic_number(), 2);
let odd = cycle_graph(5);
assert_eq!(odd.chromatic_number(), 3);
}
#[test]
fn test_chromatic_number_complete() {
for n in 1..=5 {
let g = complete_graph(n);
assert_eq!(g.chromatic_number(), n);
}
}
#[test]
fn test_empty_graph() {
let g = GraphColoring::new(4);
let c = g.greedy_coloring();
assert!(c.iter().all(|&x| x == 0));
assert!(g.is_valid_coloring(&c));
assert_eq!(g.chromatic_number(), 1);
}
#[test]
fn test_self_loop_ignored() {
let mut g = GraphColoring::new(3);
g.add_edge(0, 0).expect("unexpected None or Err"); g.add_edge(0, 1).expect("unexpected None or Err");
assert_eq!(g.degree(0), 1);
}
#[test]
fn test_invalid_edge() {
let mut g = GraphColoring::new(3);
assert!(g.add_edge(0, 5).is_err());
}
}