uni_algo/algo/algorithms/
maximal_cliques.rs1use crate::algo::GraphProjection;
11use crate::algo::algorithms::Algorithm;
12use std::collections::HashSet;
13use uni_common::core::id::Vid;
14
15pub struct MaximalCliques;
16
17#[derive(Debug, Clone)]
18pub struct MaximalCliquesConfig {
19 pub min_size: usize,
20}
21
22impl Default for MaximalCliquesConfig {
23 fn default() -> Self {
24 Self { min_size: 2 }
25 }
26}
27
28pub struct MaximalCliquesResult {
29 pub cliques: Vec<Vec<Vid>>,
30}
31
32impl Algorithm for MaximalCliques {
33 type Config = MaximalCliquesConfig;
34 type Result = MaximalCliquesResult;
35
36 fn name() -> &'static str {
37 "maximal_cliques"
38 }
39
40 fn run(graph: &GraphProjection, config: Self::Config) -> Self::Result {
41 let n = graph.vertex_count();
42 if n == 0 {
43 return MaximalCliquesResult {
44 cliques: Vec::new(),
45 };
46 }
47
48 let mut adj = vec![HashSet::new(); n];
60 for u in 0..n {
61 for &v_u32 in graph.out_neighbors(u as u32) {
62 let v = v_u32 as usize;
63 if u != v {
64 adj[u].insert(v);
65 adj[v].insert(u);
66 }
67 }
68 if graph.has_reverse() {
69 for &v_u32 in graph.in_neighbors(u as u32) {
70 let v = v_u32 as usize;
71 if u != v {
72 adj[u].insert(v);
73 adj[v].insert(u);
74 }
75 }
76 }
77 }
78
79 let mut cliques = Vec::new();
80 let r = HashSet::new();
81 let p: HashSet<usize> = (0..n).collect();
82 let x = HashSet::new();
83
84 bron_kerbosch_pivot(&adj, r, p, x, config.min_size, &mut cliques);
85
86 let mapped_cliques = cliques
87 .into_iter()
88 .map(|clique| {
89 clique
90 .into_iter()
91 .map(|idx| graph.to_vid(idx as u32))
92 .collect()
93 })
94 .collect();
95
96 MaximalCliquesResult {
97 cliques: mapped_cliques,
98 }
99 }
100}
101
102fn bron_kerbosch_pivot(
103 adj: &[HashSet<usize>],
104 r: HashSet<usize>,
105 mut p: HashSet<usize>,
106 mut x: HashSet<usize>,
107 min_size: usize,
108 cliques: &mut Vec<Vec<usize>>,
109) {
110 if p.is_empty() && x.is_empty() {
111 if r.len() >= min_size {
112 cliques.push(r.into_iter().collect());
113 }
114 return;
115 }
116
117 let u_pivot = p
119 .union(&x)
120 .max_by_key(|&u| p.iter().filter(|&v| adj[*u].contains(v)).count())
121 .copied();
122
123 let u = u_pivot.unwrap();
126
127 let candidates: Vec<usize> = p.iter().filter(|&v| !adj[u].contains(v)).copied().collect();
129
130 for v in candidates {
131 let mut new_r = r.clone();
132 new_r.insert(v);
133
134 let new_p: HashSet<usize> = p.iter().filter(|&n| adj[v].contains(n)).copied().collect();
135
136 let new_x: HashSet<usize> = x.iter().filter(|&n| adj[v].contains(n)).copied().collect();
137
138 bron_kerbosch_pivot(adj, new_r, new_p, new_x, min_size, cliques);
139
140 p.remove(&v);
141 x.insert(v);
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::algo::test_utils::build_test_graph;
149
150 #[test]
151 fn test_cliques_triangle() {
152 let vids = vec![Vid::from(0), Vid::from(1), Vid::from(2)];
155 let edges = vec![
156 (Vid::from(0), Vid::from(1)),
157 (Vid::from(1), Vid::from(2)),
158 (Vid::from(2), Vid::from(0)),
159 ];
160 let graph = build_test_graph(vids, edges);
161
162 let result = MaximalCliques::run(&graph, MaximalCliquesConfig::default());
163 assert_eq!(result.cliques.len(), 1);
164 assert_eq!(result.cliques[0].len(), 3);
165 }
166
167 #[test]
168 fn test_cliques_two_triangles() {
169 let vids = (0..5).map(Vid::from).collect();
174 let edges = vec![
175 (Vid::from(0), Vid::from(1)),
176 (Vid::from(1), Vid::from(2)),
177 (Vid::from(2), Vid::from(0)),
178 (Vid::from(2), Vid::from(3)),
179 (Vid::from(3), Vid::from(4)),
180 (Vid::from(4), Vid::from(2)),
181 ];
182 let graph = build_test_graph(vids, edges);
183
184 let result = MaximalCliques::run(&graph, MaximalCliquesConfig::default());
185 assert_eq!(result.cliques.len(), 2);
186 }
187}