oxicuda_graphalg/connectivity/
k_core.rs1use crate::error::GraphalgResult;
30use crate::repr::adjacency_list::AdjacencyList;
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct KCoreResult {
35 pub core_number: Vec<usize>,
37 pub degeneracy: usize,
39}
40
41fn symmetric_adjacency(graph: &AdjacencyList) -> GraphalgResult<Vec<Vec<usize>>> {
46 let n = graph.n;
47 let mut raw: Vec<Vec<usize>> = vec![Vec::new(); n];
52 for u in 0..n {
53 for &v in graph.neighbors(u)? {
54 if v == u {
55 continue; }
57 raw[u].push(v);
58 raw[v].push(u);
59 }
60 }
61 let mut sym: Vec<Vec<usize>> = vec![Vec::new(); n];
64 let mut seen: Vec<usize> = vec![usize::MAX; n];
65 for u in 0..n {
66 for &v in &raw[u] {
67 if seen[v] != u {
68 seen[v] = u;
69 sym[u].push(v);
70 }
71 }
72 }
73 Ok(sym)
74}
75
76fn batagelj_zaversnik(sym: &[Vec<usize>]) -> (Vec<usize>, Vec<usize>) {
79 let n = sym.len();
80 if n == 0 {
81 return (Vec::new(), Vec::new());
82 }
83
84 let mut deg: Vec<usize> = sym.iter().map(|adj| adj.len()).collect();
86 let max_deg = deg.iter().copied().max().unwrap_or(0);
87
88 let mut bin: Vec<usize> = vec![0usize; max_deg + 1];
90 for &d in ° {
91 bin[d] += 1;
92 }
93 let mut start = 0usize;
95 for d in 0..=max_deg {
96 let count = bin[d];
97 bin[d] = start;
98 start += count;
99 }
100
101 let mut pos: Vec<usize> = vec![0usize; n];
103 let mut vert: Vec<usize> = vec![0usize; n];
104 for v in 0..n {
105 pos[v] = bin[deg[v]];
106 vert[pos[v]] = v;
107 bin[deg[v]] += 1;
108 }
109 for d in (1..=max_deg).rev() {
111 bin[d] = bin[d - 1];
112 }
113 bin[0] = 0;
114
115 let mut core: Vec<usize> = vec![0usize; n];
117 let mut order: Vec<usize> = Vec::with_capacity(n);
118 for i in 0..n {
119 let v = vert[i];
120 core[v] = deg[v];
121 order.push(v);
122 for &u in &sym[v] {
125 if deg[u] > deg[v] {
126 let du = deg[u];
127 let pu = pos[u];
128 let pw = bin[du];
129 let w = vert[pw];
130 if u != w {
131 vert[pu] = w;
133 vert[pw] = u;
134 pos[w] = pu;
135 pos[u] = pw;
136 }
137 bin[du] += 1;
140 deg[u] = du - 1;
141 }
142 }
143 }
144
145 (core, order)
146}
147
148pub fn k_core_decomposition(graph: &AdjacencyList) -> GraphalgResult<KCoreResult> {
153 let sym = symmetric_adjacency(graph)?;
154 let (core_number, _order) = batagelj_zaversnik(&sym);
155 let degeneracy = core_number.iter().copied().max().unwrap_or(0);
156 Ok(KCoreResult {
157 core_number,
158 degeneracy,
159 })
160}
161
162pub fn core_numbers(graph: &AdjacencyList) -> GraphalgResult<Vec<usize>> {
164 Ok(k_core_decomposition(graph)?.core_number)
165}
166
167pub fn degeneracy(graph: &AdjacencyList) -> GraphalgResult<usize> {
169 Ok(k_core_decomposition(graph)?.degeneracy)
170}
171
172pub fn degeneracy_ordering(graph: &AdjacencyList) -> GraphalgResult<Vec<usize>> {
176 let sym = symmetric_adjacency(graph)?;
177 let (_core, order) = batagelj_zaversnik(&sym);
178 Ok(order)
179}
180
181pub fn k_core_subgraph(graph: &AdjacencyList, k: usize) -> GraphalgResult<Vec<usize>> {
187 let core = core_numbers(graph)?;
188 Ok((0..core.len()).filter(|&v| core[v] >= k).collect())
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 fn clique(n: usize) -> AdjacencyList {
197 let mut g = AdjacencyList::new(n);
198 for u in 0..n {
199 for v in (u + 1)..n {
200 g.add_undirected_edge(u, v).expect("edge");
201 }
202 }
203 g
204 }
205
206 fn path(n: usize) -> AdjacencyList {
208 let mut g = AdjacencyList::new(n);
209 for i in 0..n.saturating_sub(1) {
210 g.add_undirected_edge(i, i + 1).expect("edge");
211 }
212 g
213 }
214
215 fn cycle(n: usize) -> AdjacencyList {
217 let mut g = AdjacencyList::new(n);
218 for i in 0..n {
219 g.add_undirected_edge(i, (i + 1) % n).expect("edge");
220 }
221 g
222 }
223
224 fn star(leaves: usize) -> AdjacencyList {
226 let mut g = AdjacencyList::new(leaves + 1);
227 for leaf in 1..=leaves {
228 g.add_undirected_edge(0, leaf).expect("edge");
229 }
230 g
231 }
232
233 #[test]
234 fn clique_k5_all_core_four() {
235 let g = clique(5);
236 let res = k_core_decomposition(&g).expect("kcore");
237 assert_eq!(res.core_number, vec![4, 4, 4, 4, 4]);
238 assert_eq!(res.degeneracy, 4);
239 }
240
241 #[test]
242 fn path_p5_all_core_one() {
243 let g = path(5);
244 let res = k_core_decomposition(&g).expect("kcore");
245 assert_eq!(res.core_number, vec![1, 1, 1, 1, 1]);
246 assert_eq!(res.degeneracy, 1);
247 }
248
249 #[test]
250 fn cycle_c5_all_core_two() {
251 let g = cycle(5);
252 let res = k_core_decomposition(&g).expect("kcore");
253 assert_eq!(res.core_number, vec![2, 2, 2, 2, 2]);
254 assert_eq!(res.degeneracy, 2);
255 }
256
257 #[test]
258 fn star_all_core_one() {
259 let g = star(4);
260 let res = k_core_decomposition(&g).expect("kcore");
261 assert_eq!(res.core_number, vec![1, 1, 1, 1, 1]);
262 assert_eq!(res.degeneracy, 1);
263 }
264
265 #[test]
266 fn tree_degeneracy_one() {
267 let mut g = AdjacencyList::new(6);
269 for (u, v) in [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5)] {
270 g.add_undirected_edge(u, v).expect("edge");
271 }
272 let res = k_core_decomposition(&g).expect("kcore");
273 assert_eq!(res.degeneracy, 1);
274 assert!(res.core_number.iter().all(|&c| c == 1));
275 }
276
277 #[test]
278 fn triangle_plus_pendant() {
279 let mut g = AdjacencyList::new(4);
281 g.add_undirected_edge(0, 1).expect("edge");
282 g.add_undirected_edge(1, 2).expect("edge");
283 g.add_undirected_edge(0, 2).expect("edge");
284 g.add_undirected_edge(3, 2).expect("edge");
285 let res = k_core_decomposition(&g).expect("kcore");
286 assert_eq!(res.core_number, vec![2, 2, 2, 1]);
287 assert_eq!(res.degeneracy, 2);
288 }
289
290 #[test]
291 fn directed_single_edge_equals_undirected() {
292 let mut directed = AdjacencyList::new(3);
295 directed.add_edge(0, 1).expect("edge");
296 directed.add_edge(1, 2).expect("edge");
297 directed.add_edge(2, 0).expect("edge");
298 let res = core_numbers(&directed).expect("kcore");
299 assert_eq!(res, vec![2, 2, 2]); }
301
302 #[test]
303 fn parallel_edges_deduplicated() {
304 let mut g = AdjacencyList::new(3);
306 g.add_undirected_edge(0, 1).expect("edge");
307 g.add_undirected_edge(0, 1).expect("edge");
308 g.add_undirected_edge(1, 2).expect("edge");
309 let res = core_numbers(&g).expect("kcore");
310 assert_eq!(res, vec![1, 1, 1]);
311 }
312
313 #[test]
314 fn self_loops_ignored() {
315 let mut g = AdjacencyList::new(2);
316 g.add_undirected_edge(0, 0).expect("edge"); g.add_undirected_edge(0, 1).expect("edge");
318 let res = core_numbers(&g).expect("kcore");
319 assert_eq!(res, vec![1, 1]);
320 }
321
322 #[test]
323 fn k_core_subgraph_nesting() {
324 let mut g = AdjacencyList::new(4);
326 g.add_undirected_edge(0, 1).expect("edge");
327 g.add_undirected_edge(1, 2).expect("edge");
328 g.add_undirected_edge(0, 2).expect("edge");
329 g.add_undirected_edge(3, 2).expect("edge");
330 let degen = degeneracy(&g).expect("degeneracy");
331 for k in 1..=(degen + 1) {
332 let hi = k_core_subgraph(&g, k).expect("subgraph");
333 let lo = k_core_subgraph(&g, k - 1).expect("subgraph");
334 for &v in &hi {
336 assert!(lo.contains(&v), "nesting violated at k={k} vertex {v}");
337 }
338 }
339 assert_eq!(k_core_subgraph(&g, 2).expect("sg"), vec![0, 1, 2]);
341 assert_eq!(k_core_subgraph(&g, 1).expect("sg"), vec![0, 1, 2, 3]);
342 assert_eq!(k_core_subgraph(&g, 0).expect("sg"), vec![0, 1, 2, 3]);
343 }
344
345 #[test]
346 fn degeneracy_ordering_is_permutation() {
347 let g = clique(5);
348 let mut order = degeneracy_ordering(&g).expect("order");
349 assert_eq!(order.len(), 5);
350 order.sort_unstable();
351 assert_eq!(order, vec![0, 1, 2, 3, 4]);
352 }
353
354 #[test]
355 fn empty_graph() {
356 let g = AdjacencyList::new(0);
357 let res = k_core_decomposition(&g).expect("kcore");
358 assert!(res.core_number.is_empty());
359 assert_eq!(res.degeneracy, 0);
360 assert!(degeneracy_ordering(&g).expect("order").is_empty());
361 assert!(k_core_subgraph(&g, 1).expect("sg").is_empty());
362 }
363
364 #[test]
365 fn single_vertex() {
366 let g = AdjacencyList::new(1);
367 let res = k_core_decomposition(&g).expect("kcore");
368 assert_eq!(res.core_number, vec![0]);
369 assert_eq!(res.degeneracy, 0);
370 assert_eq!(degeneracy_ordering(&g).expect("order"), vec![0]);
371 assert_eq!(k_core_subgraph(&g, 0).expect("sg"), vec![0]);
372 assert!(k_core_subgraph(&g, 1).expect("sg").is_empty());
373 }
374
375 #[test]
376 fn isolated_vertices_core_zero() {
377 let mut g = AdjacencyList::new(4);
379 g.add_undirected_edge(0, 1).expect("edge");
380 let res = core_numbers(&g).expect("kcore");
381 assert_eq!(res, vec![1, 1, 0, 0]);
382 assert_eq!(degeneracy(&g).expect("d"), 1);
383 }
384
385 #[test]
386 fn complete_bipartite_k33() {
387 let mut g = AdjacencyList::new(6);
391 for u in 0..3 {
392 for v in 3..6 {
393 g.add_undirected_edge(u, v).expect("edge");
394 }
395 }
396 let res = k_core_decomposition(&g).expect("kcore");
397 assert_eq!(res.core_number, vec![3, 3, 3, 3, 3, 3]);
398 assert_eq!(res.degeneracy, 3);
399 }
400}