use std::collections::HashSet;
use crate::error::GraphalgResult;
use crate::repr::AdjacencyList;
fn build_adj_sets(g: &AdjacencyList) -> Vec<HashSet<usize>> {
(0..g.n)
.map(|u| {
g.edges[u]
.iter()
.copied()
.filter(|&v| v != u) .collect()
})
.collect()
}
fn pick_pivot(p: &HashSet<usize>, x: &HashSet<usize>, adj: &[HashSet<usize>]) -> usize {
let mut best_u = usize::MAX;
let mut best_count = 0usize;
for &u in p.iter().chain(x.iter()) {
let count = p.intersection(&adj[u]).count();
if best_u == usize::MAX || count > best_count {
best_count = count;
best_u = u;
}
}
best_u
}
fn bk_pivot(
r: &mut Vec<usize>,
p: &mut HashSet<usize>,
x: &mut HashSet<usize>,
adj: &[HashSet<usize>],
result: &mut Vec<Vec<usize>>,
) {
if p.is_empty() && x.is_empty() {
let mut clique = r.clone();
clique.sort_unstable();
result.push(clique);
return;
}
if p.is_empty() {
return;
}
let pivot = pick_pivot(p, x, adj);
let mut candidates: Vec<usize> = p
.iter()
.copied()
.filter(|v| !adj[pivot].contains(v))
.collect();
candidates.sort_unstable();
for v in candidates {
let new_p: HashSet<usize> = p.intersection(&adj[v]).copied().collect();
let new_x: HashSet<usize> = x.intersection(&adj[v]).copied().collect();
r.push(v);
bk_pivot(r, &mut { new_p }, &mut { new_x }, adj, result);
r.pop();
p.remove(&v);
x.insert(v);
}
}
fn compute_degeneracy_ordering(g: &AdjacencyList) -> (Vec<usize>, Vec<usize>) {
let n = g.n;
let mut degrees: Vec<usize> = (0..n).map(|u| g.edges[u].len()).collect();
let mut removed = vec![false; n];
let mut ordering = Vec::with_capacity(n);
for _ in 0..n {
let v = (0..n)
.filter(|&u| !removed[u])
.min_by_key(|&u| degrees[u])
.unwrap_or(0);
ordering.push(v);
removed[v] = true;
for &w in &g.edges[v] {
if !removed[w] && w != v {
degrees[w] = degrees[w].saturating_sub(1);
}
}
}
let mut position = vec![0usize; n];
for (idx, &v) in ordering.iter().enumerate() {
position[v] = idx;
}
(ordering, position)
}
pub fn bron_kerbosch(g: &AdjacencyList) -> GraphalgResult<Vec<Vec<usize>>> {
if g.n == 0 {
return Ok(Vec::new());
}
let adj = build_adj_sets(g);
let p: HashSet<usize> = (0..g.n).collect();
let x: HashSet<usize> = HashSet::new();
let mut result = Vec::new();
bk_pivot(&mut Vec::new(), &mut { p }, &mut { x }, &adj, &mut result);
result.sort_unstable();
Ok(result)
}
pub fn bron_kerbosch_degeneracy(g: &AdjacencyList) -> GraphalgResult<Vec<Vec<usize>>> {
if g.n == 0 {
return Ok(Vec::new());
}
let adj = build_adj_sets(g);
let (ordering, position) = compute_degeneracy_ordering(g);
let mut result = Vec::new();
for &v in &ordering {
let pos_v = position[v];
let p: HashSet<usize> = adj[v]
.iter()
.copied()
.filter(|&u| position[u] > pos_v)
.collect();
let x: HashSet<usize> = adj[v]
.iter()
.copied()
.filter(|&u| position[u] < pos_v)
.collect();
let mut r = vec![v];
bk_pivot(&mut r, &mut { p }, &mut { x }, &adj, &mut result);
}
result.sort_unstable();
Ok(result)
}
pub fn maximum_clique(g: &AdjacencyList) -> GraphalgResult<Vec<usize>> {
if g.n == 0 {
return Ok(Vec::new());
}
let all = bron_kerbosch(g)?;
if all.is_empty() {
return Ok(Vec::new());
}
let best = all.into_iter().max_by_key(|c| c.len()).unwrap_or_default();
Ok(best)
}
#[cfg(test)]
mod tests {
use super::*;
fn undirected(n: usize, edges: &[(usize, usize)]) -> AdjacencyList {
let mut g = AdjacencyList::new(n);
for &(u, v) in edges {
g.add_undirected_edge(u, v).expect("add ok");
}
g
}
fn check_cliques_genuine(g: &AdjacencyList, cliques: &[Vec<usize>]) {
let adj = build_adj_sets(g);
for clique in cliques {
for i in 0..clique.len() {
for j in (i + 1)..clique.len() {
assert!(
adj[clique[i]].contains(&clique[j]),
"Edge ({},{}) missing — clique {:?} is not a clique",
clique[i],
clique[j],
clique
);
}
}
}
}
fn check_cliques_maximal(g: &AdjacencyList, cliques: &[Vec<usize>]) {
let adj = build_adj_sets(g);
let clique_set: Vec<HashSet<usize>> = cliques
.iter()
.map(|c| c.iter().copied().collect())
.collect();
for (cidx, clique) in cliques.iter().enumerate() {
let members = &clique_set[cidx];
for v in 0..g.n {
if members.contains(&v) {
continue;
}
let adj_to_all = clique.iter().all(|&u| adj[v].contains(&u));
assert!(
!adj_to_all,
"Vertex {} can extend clique {:?} — not maximal",
v, clique
);
}
}
}
#[test]
fn k3_triangle() {
let g = undirected(3, &[(0, 1), (1, 2), (0, 2)]);
let cliques = bron_kerbosch(&g).expect("ok");
assert_eq!(cliques, vec![vec![0usize, 1, 2]]);
}
#[test]
fn k5_complete() {
let g = undirected(
5,
&[
(0, 1),
(0, 2),
(0, 3),
(0, 4),
(1, 2),
(1, 3),
(1, 4),
(2, 3),
(2, 4),
(3, 4),
],
);
let cliques = bron_kerbosch(&g).expect("ok");
assert_eq!(cliques, vec![vec![0usize, 1, 2, 3, 4]]);
}
#[test]
fn edgeless_n4() {
let g = AdjacencyList::new(4);
let mut cliques = bron_kerbosch(&g).expect("ok");
cliques.sort_unstable();
let expected: Vec<Vec<usize>> = vec![vec![0], vec![1], vec![2], vec![3]];
assert_eq!(cliques, expected);
}
#[test]
fn path_three_nodes() {
let g = undirected(3, &[(0, 1), (1, 2)]);
let cliques = bron_kerbosch(&g).expect("ok");
assert_eq!(cliques.len(), 2);
assert!(cliques.contains(&vec![0usize, 1]));
assert!(cliques.contains(&vec![1usize, 2]));
}
#[test]
fn two_disjoint_triangles() {
let g = undirected(
6,
&[
(0, 1),
(1, 2),
(0, 2), (3, 4),
(4, 5),
(3, 5), ],
);
let cliques = bron_kerbosch(&g).expect("ok");
assert_eq!(cliques.len(), 2);
assert!(cliques.contains(&vec![0usize, 1, 2]));
assert!(cliques.contains(&vec![3usize, 4, 5]));
}
#[test]
fn c4_cycle() {
let g = undirected(4, &[(0, 1), (1, 2), (2, 3), (0, 3)]);
let cliques = bron_kerbosch(&g).expect("ok");
assert_eq!(
cliques.len(),
4,
"C4 should have 4 maximal cliques, got {:?}",
cliques
);
for c in &cliques {
assert_eq!(c.len(), 2, "Each C4 clique should be an edge: {:?}", c);
}
}
#[test]
fn c5_cycle() {
let g = undirected(5, &[(0, 1), (1, 2), (2, 3), (3, 4), (0, 4)]);
let cliques = bron_kerbosch(&g).expect("ok");
assert_eq!(
cliques.len(),
5,
"C5 should have 5 maximal cliques, got {:?}",
cliques
);
for c in &cliques {
assert_eq!(c.len(), 2);
}
}
#[test]
fn maximum_clique_picks_triangle() {
let g = undirected(5, &[(0, 1), (1, 2), (0, 2), (3, 4)]);
let mc = maximum_clique(&g).expect("ok");
assert_eq!(
mc.len(),
3,
"max clique should be the triangle, got {:?}",
mc
);
}
#[test]
fn all_cliques_genuine() {
let g = undirected(
7,
&[
(0, 1),
(1, 2),
(0, 2), (3, 4),
(4, 5),
(3, 5), (0, 3),
(2, 5),
(1, 6), ],
);
let cliques = bron_kerbosch(&g).expect("ok");
check_cliques_genuine(&g, &cliques);
}
#[test]
fn all_cliques_maximal() {
let g = undirected(
7,
&[
(0, 1),
(1, 2),
(0, 2),
(3, 4),
(4, 5),
(3, 5),
(0, 3),
(2, 5),
(1, 6),
],
);
let cliques = bron_kerbosch(&g).expect("ok");
check_cliques_maximal(&g, &cliques);
}
#[test]
fn pivot_vs_degeneracy_identical() {
let g = undirected(
7,
&[
(0, 1),
(1, 2),
(0, 2),
(3, 4),
(4, 5),
(3, 5),
(0, 3),
(2, 5),
(1, 6),
],
);
let mut c1 = bron_kerbosch(&g).expect("pivot");
let mut c2 = bron_kerbosch_degeneracy(&g).expect("degeneracy");
c1.sort_unstable();
c2.sort_unstable();
assert_eq!(
c1, c2,
"pivot and degeneracy must return the same clique set"
);
}
#[test]
fn single_vertex() {
let g = AdjacencyList::new(1);
let cliques = bron_kerbosch(&g).expect("ok");
assert_eq!(cliques, vec![vec![0usize]]);
}
#[test]
fn empty_graph() {
let g = AdjacencyList::new(0);
let cliques = bron_kerbosch(&g).expect("ok");
assert!(cliques.is_empty());
}
#[test]
fn self_loop_no_degenerate_clique() {
let mut g = AdjacencyList::new(3);
g.add_undirected_edge(0, 0).expect("self-loop ok");
g.add_undirected_edge(0, 1).expect("ok");
let cliques = bron_kerbosch(&g).expect("ok");
for c in &cliques {
let unique: HashSet<usize> = c.iter().copied().collect();
assert_eq!(
unique.len(),
c.len(),
"clique has duplicate vertex: {:?}",
c
);
}
}
#[test]
fn deterministic() {
let g = undirected(6, &[(0, 1), (1, 2), (0, 2), (3, 4), (4, 5), (3, 5)]);
let r1 = bron_kerbosch(&g).expect("r1");
let r2 = bron_kerbosch(&g).expect("r2");
assert_eq!(r1, r2);
}
#[test]
fn maximum_clique_empty_graph() {
let g = AdjacencyList::new(0);
let mc = maximum_clique(&g).expect("ok");
assert!(mc.is_empty());
}
#[test]
fn maximum_clique_k5() {
let g = undirected(
5,
&[
(0, 1),
(0, 2),
(0, 3),
(0, 4),
(1, 2),
(1, 3),
(1, 4),
(2, 3),
(2, 4),
(3, 4),
],
);
let mc = maximum_clique(&g).expect("ok");
assert_eq!(mc.len(), 5);
}
#[test]
fn maximum_clique_path() {
let g = undirected(4, &[(0, 1), (1, 2), (2, 3)]);
let mc = maximum_clique(&g).expect("ok");
assert_eq!(mc.len(), 2);
}
#[test]
fn clique_number_three() {
let g = undirected(
5,
&[
(0, 1),
(0, 2),
(1, 2), (0, 3),
(2, 3), (0, 4),
(3, 4), ],
);
let mc = maximum_clique(&g).expect("ok");
assert_eq!(mc.len(), 3, "max clique size should be 3, got {:?}", mc);
}
#[test]
fn all_cliques_sorted_ascending() {
let g = undirected(6, &[(0, 1), (1, 2), (0, 2), (3, 4), (4, 5), (3, 5), (0, 3)]);
let cliques = bron_kerbosch(&g).expect("ok");
for c in &cliques {
let sorted: Vec<usize> = {
let mut tmp = c.clone();
tmp.sort_unstable();
tmp
};
assert_eq!(c, &sorted, "clique not sorted: {:?}", c);
}
}
#[test]
fn k10_complete() {
let mut g = AdjacencyList::new(10);
for i in 0..10 {
for j in (i + 1)..10 {
g.add_undirected_edge(i, j).expect("ok");
}
}
let cliques = bron_kerbosch(&g).expect("ok");
assert_eq!(cliques.len(), 1, "K10 should have exactly 1 maximal clique");
assert_eq!(cliques[0].len(), 10);
}
#[test]
fn petersen_no_triangle() {
let g = undirected(
10,
&[
(0, 1),
(1, 2),
(2, 3),
(3, 4),
(4, 0),
(5, 7),
(7, 9),
(9, 6),
(6, 8),
(8, 5),
(0, 5),
(1, 6),
(2, 7),
(3, 8),
(4, 9),
],
);
let cliques = bron_kerbosch(&g).expect("ok");
for c in &cliques {
assert!(
c.len() <= 2,
"Petersen graph has no triangle; found clique of size {}: {:?}",
c.len(),
c
);
}
}
}