use crate::error::GraphalgResult;
use crate::repr::adjacency_list::AdjacencyList;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KCoreResult {
pub core_number: Vec<usize>,
pub degeneracy: usize,
}
fn symmetric_adjacency(graph: &AdjacencyList) -> GraphalgResult<Vec<Vec<usize>>> {
let n = graph.n;
let mut raw: Vec<Vec<usize>> = vec![Vec::new(); n];
for u in 0..n {
for &v in graph.neighbors(u)? {
if v == u {
continue; }
raw[u].push(v);
raw[v].push(u);
}
}
let mut sym: Vec<Vec<usize>> = vec![Vec::new(); n];
let mut seen: Vec<usize> = vec![usize::MAX; n];
for u in 0..n {
for &v in &raw[u] {
if seen[v] != u {
seen[v] = u;
sym[u].push(v);
}
}
}
Ok(sym)
}
fn batagelj_zaversnik(sym: &[Vec<usize>]) -> (Vec<usize>, Vec<usize>) {
let n = sym.len();
if n == 0 {
return (Vec::new(), Vec::new());
}
let mut deg: Vec<usize> = sym.iter().map(|adj| adj.len()).collect();
let max_deg = deg.iter().copied().max().unwrap_or(0);
let mut bin: Vec<usize> = vec![0usize; max_deg + 1];
for &d in ° {
bin[d] += 1;
}
let mut start = 0usize;
for d in 0..=max_deg {
let count = bin[d];
bin[d] = start;
start += count;
}
let mut pos: Vec<usize> = vec![0usize; n];
let mut vert: Vec<usize> = vec![0usize; n];
for v in 0..n {
pos[v] = bin[deg[v]];
vert[pos[v]] = v;
bin[deg[v]] += 1;
}
for d in (1..=max_deg).rev() {
bin[d] = bin[d - 1];
}
bin[0] = 0;
let mut core: Vec<usize> = vec![0usize; n];
let mut order: Vec<usize> = Vec::with_capacity(n);
for i in 0..n {
let v = vert[i];
core[v] = deg[v];
order.push(v);
for &u in &sym[v] {
if deg[u] > deg[v] {
let du = deg[u];
let pu = pos[u];
let pw = bin[du];
let w = vert[pw];
if u != w {
vert[pu] = w;
vert[pw] = u;
pos[w] = pu;
pos[u] = pw;
}
bin[du] += 1;
deg[u] = du - 1;
}
}
}
(core, order)
}
pub fn k_core_decomposition(graph: &AdjacencyList) -> GraphalgResult<KCoreResult> {
let sym = symmetric_adjacency(graph)?;
let (core_number, _order) = batagelj_zaversnik(&sym);
let degeneracy = core_number.iter().copied().max().unwrap_or(0);
Ok(KCoreResult {
core_number,
degeneracy,
})
}
pub fn core_numbers(graph: &AdjacencyList) -> GraphalgResult<Vec<usize>> {
Ok(k_core_decomposition(graph)?.core_number)
}
pub fn degeneracy(graph: &AdjacencyList) -> GraphalgResult<usize> {
Ok(k_core_decomposition(graph)?.degeneracy)
}
pub fn degeneracy_ordering(graph: &AdjacencyList) -> GraphalgResult<Vec<usize>> {
let sym = symmetric_adjacency(graph)?;
let (_core, order) = batagelj_zaversnik(&sym);
Ok(order)
}
pub fn k_core_subgraph(graph: &AdjacencyList, k: usize) -> GraphalgResult<Vec<usize>> {
let core = core_numbers(graph)?;
Ok((0..core.len()).filter(|&v| core[v] >= k).collect())
}
#[cfg(test)]
mod tests {
use super::*;
fn clique(n: usize) -> AdjacencyList {
let mut g = AdjacencyList::new(n);
for u in 0..n {
for v in (u + 1)..n {
g.add_undirected_edge(u, v).expect("edge");
}
}
g
}
fn path(n: usize) -> AdjacencyList {
let mut g = AdjacencyList::new(n);
for i in 0..n.saturating_sub(1) {
g.add_undirected_edge(i, i + 1).expect("edge");
}
g
}
fn cycle(n: usize) -> AdjacencyList {
let mut g = AdjacencyList::new(n);
for i in 0..n {
g.add_undirected_edge(i, (i + 1) % n).expect("edge");
}
g
}
fn star(leaves: usize) -> AdjacencyList {
let mut g = AdjacencyList::new(leaves + 1);
for leaf in 1..=leaves {
g.add_undirected_edge(0, leaf).expect("edge");
}
g
}
#[test]
fn clique_k5_all_core_four() {
let g = clique(5);
let res = k_core_decomposition(&g).expect("kcore");
assert_eq!(res.core_number, vec![4, 4, 4, 4, 4]);
assert_eq!(res.degeneracy, 4);
}
#[test]
fn path_p5_all_core_one() {
let g = path(5);
let res = k_core_decomposition(&g).expect("kcore");
assert_eq!(res.core_number, vec![1, 1, 1, 1, 1]);
assert_eq!(res.degeneracy, 1);
}
#[test]
fn cycle_c5_all_core_two() {
let g = cycle(5);
let res = k_core_decomposition(&g).expect("kcore");
assert_eq!(res.core_number, vec![2, 2, 2, 2, 2]);
assert_eq!(res.degeneracy, 2);
}
#[test]
fn star_all_core_one() {
let g = star(4);
let res = k_core_decomposition(&g).expect("kcore");
assert_eq!(res.core_number, vec![1, 1, 1, 1, 1]);
assert_eq!(res.degeneracy, 1);
}
#[test]
fn tree_degeneracy_one() {
let mut g = AdjacencyList::new(6);
for (u, v) in [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5)] {
g.add_undirected_edge(u, v).expect("edge");
}
let res = k_core_decomposition(&g).expect("kcore");
assert_eq!(res.degeneracy, 1);
assert!(res.core_number.iter().all(|&c| c == 1));
}
#[test]
fn triangle_plus_pendant() {
let mut g = AdjacencyList::new(4);
g.add_undirected_edge(0, 1).expect("edge");
g.add_undirected_edge(1, 2).expect("edge");
g.add_undirected_edge(0, 2).expect("edge");
g.add_undirected_edge(3, 2).expect("edge");
let res = k_core_decomposition(&g).expect("kcore");
assert_eq!(res.core_number, vec![2, 2, 2, 1]);
assert_eq!(res.degeneracy, 2);
}
#[test]
fn directed_single_edge_equals_undirected() {
let mut directed = AdjacencyList::new(3);
directed.add_edge(0, 1).expect("edge");
directed.add_edge(1, 2).expect("edge");
directed.add_edge(2, 0).expect("edge");
let res = core_numbers(&directed).expect("kcore");
assert_eq!(res, vec![2, 2, 2]); }
#[test]
fn parallel_edges_deduplicated() {
let mut g = AdjacencyList::new(3);
g.add_undirected_edge(0, 1).expect("edge");
g.add_undirected_edge(0, 1).expect("edge");
g.add_undirected_edge(1, 2).expect("edge");
let res = core_numbers(&g).expect("kcore");
assert_eq!(res, vec![1, 1, 1]);
}
#[test]
fn self_loops_ignored() {
let mut g = AdjacencyList::new(2);
g.add_undirected_edge(0, 0).expect("edge"); g.add_undirected_edge(0, 1).expect("edge");
let res = core_numbers(&g).expect("kcore");
assert_eq!(res, vec![1, 1]);
}
#[test]
fn k_core_subgraph_nesting() {
let mut g = AdjacencyList::new(4);
g.add_undirected_edge(0, 1).expect("edge");
g.add_undirected_edge(1, 2).expect("edge");
g.add_undirected_edge(0, 2).expect("edge");
g.add_undirected_edge(3, 2).expect("edge");
let degen = degeneracy(&g).expect("degeneracy");
for k in 1..=(degen + 1) {
let hi = k_core_subgraph(&g, k).expect("subgraph");
let lo = k_core_subgraph(&g, k - 1).expect("subgraph");
for &v in &hi {
assert!(lo.contains(&v), "nesting violated at k={k} vertex {v}");
}
}
assert_eq!(k_core_subgraph(&g, 2).expect("sg"), vec![0, 1, 2]);
assert_eq!(k_core_subgraph(&g, 1).expect("sg"), vec![0, 1, 2, 3]);
assert_eq!(k_core_subgraph(&g, 0).expect("sg"), vec![0, 1, 2, 3]);
}
#[test]
fn degeneracy_ordering_is_permutation() {
let g = clique(5);
let mut order = degeneracy_ordering(&g).expect("order");
assert_eq!(order.len(), 5);
order.sort_unstable();
assert_eq!(order, vec![0, 1, 2, 3, 4]);
}
#[test]
fn empty_graph() {
let g = AdjacencyList::new(0);
let res = k_core_decomposition(&g).expect("kcore");
assert!(res.core_number.is_empty());
assert_eq!(res.degeneracy, 0);
assert!(degeneracy_ordering(&g).expect("order").is_empty());
assert!(k_core_subgraph(&g, 1).expect("sg").is_empty());
}
#[test]
fn single_vertex() {
let g = AdjacencyList::new(1);
let res = k_core_decomposition(&g).expect("kcore");
assert_eq!(res.core_number, vec![0]);
assert_eq!(res.degeneracy, 0);
assert_eq!(degeneracy_ordering(&g).expect("order"), vec![0]);
assert_eq!(k_core_subgraph(&g, 0).expect("sg"), vec![0]);
assert!(k_core_subgraph(&g, 1).expect("sg").is_empty());
}
#[test]
fn isolated_vertices_core_zero() {
let mut g = AdjacencyList::new(4);
g.add_undirected_edge(0, 1).expect("edge");
let res = core_numbers(&g).expect("kcore");
assert_eq!(res, vec![1, 1, 0, 0]);
assert_eq!(degeneracy(&g).expect("d"), 1);
}
#[test]
fn complete_bipartite_k33() {
let mut g = AdjacencyList::new(6);
for u in 0..3 {
for v in 3..6 {
g.add_undirected_edge(u, v).expect("edge");
}
}
let res = k_core_decomposition(&g).expect("kcore");
assert_eq!(res.core_number, vec![3, 3, 3, 3, 3, 3]);
assert_eq!(res.degeneracy, 3);
}
}