Skip to content

Commit 5c61369

Browse files
gzsomborsamueltardieu
authored andcommitted
feat: implement Bron-Kerbosch algorithm
to find maximal cliques in a graph.
1 parent 50c6c39 commit 5c61369

File tree

4 files changed

+161
-0
lines changed

4 files changed

+161
-0
lines changed

src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
//! - [connected components](undirected/connected_components/index.html): find disjoint connected sets of vertices ([⇒ Wikipedia][Connected components])
3434
//! - [Kruskal](undirected/kruskal/index.html): find a minimum-spanning-tree ([⇒ Wikipedia][Kruskal])
3535
//! - [Prim](undirected/prim/index.html): find a minimum-spanning-tree ([⇒ Wikipedia][Prim])
36+
//! - [cliques]: find maximum cliques in a graph ([= Wikipedia][BronKerbosch])
3637
//!
3738
//! ### Matching
3839
//!
@@ -80,6 +81,7 @@
8081
//! [A*]: https://en.wikipedia.org/wiki/A*_search_algorithm
8182
//! [BFS]: https://en.wikipedia.org/wiki/Breadth-first_search
8283
//! [Brent]: https://en.wikipedia.org/wiki/Cycle_detection#Brent's_algorithm
84+
//! [BronKerbosch]: https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm
8385
//! [Connected components]: https://en.wikipedia.org/wiki/Connected_component_(graph_theory)
8486
//! [DFS]: https://en.wikipedia.org/wiki/Depth-first_search
8587
//! [Dijkstra]: https://en.wikipedia.org/wiki/Dijkstra's_algorithm
@@ -131,6 +133,7 @@ pub mod prelude {
131133
pub use crate::grid::*;
132134
pub use crate::kuhn_munkres::*;
133135
pub use crate::matrix::*;
136+
pub use crate::undirected::cliques::*;
134137
pub use crate::undirected::connected_components::*;
135138
pub use crate::undirected::kruskal::*;
136139
pub use crate::utils::*;

src/undirected/cliques.rs

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//! Find cliques in an undirected graph.
2+
3+
use std::collections::HashSet;
4+
use std::hash::Hash;
5+
6+
/// Algorithm for finding all maximal cliques in an undirected graph.
7+
/// That is, it lists all subsets of vertices with the two properties that each pair of vertices in
8+
/// one of the listed subsets is connected by an edge, and no listed subset can have
9+
/// any additional vertices added to it while preserving its complete connectivity.
10+
/// [Bron-Kerbosch algorithm](https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm).
11+
///
12+
///
13+
/// - `vertices` is the list of all nodes.
14+
/// - `connected` returns true if the two given node is connected.
15+
/// - return a list of cliques.
16+
pub fn maximal_cliques_collect<N, FN, IN>(vertices: IN, connected: &mut FN) -> Vec<HashSet<N>>
17+
where
18+
N: Eq + Hash + Clone,
19+
FN: FnMut(&N, &N) -> bool,
20+
IN: IntoIterator<Item = N>,
21+
{
22+
let mut result = Vec::new();
23+
let mut consumer = |n: &HashSet<N>| result.push(n.to_owned());
24+
let mut remaining_nodes: HashSet<N> = vertices.into_iter().collect::<HashSet<_>>();
25+
bron_kerbosch(
26+
connected,
27+
&HashSet::new(),
28+
&mut remaining_nodes,
29+
&mut HashSet::new(),
30+
&mut consumer,
31+
);
32+
result
33+
}
34+
35+
/// Algorithm for finding all maximal cliques in an undirected graph.
36+
/// That is, it lists all subsets of vertices with the two properties that each pair of vertices in
37+
/// one of the listed subsets is connected by an edge, and no listed subset can have
38+
/// any additional vertices added to it while preserving its complete connectivity.
39+
/// [Bron-Kerbosch algorithm](https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm).
40+
///
41+
///
42+
/// - `vertices` is the list of all nodes.
43+
/// - `connected` returns true if the two given node is connected.
44+
/// - 'consumer' function which called for each clique.
45+
///
46+
pub fn maximal_cliques<N, FN, IN, CO>(vertices: IN, connected: &mut FN, consumer: &mut CO)
47+
where
48+
N: Eq + Hash + Clone,
49+
FN: FnMut(&N, &N) -> bool,
50+
IN: IntoIterator<Item = N>,
51+
CO: FnMut(&HashSet<N>),
52+
{
53+
let mut remaining_nodes: HashSet<N> = vertices.into_iter().collect();
54+
bron_kerbosch(
55+
connected,
56+
&HashSet::new(),
57+
&mut remaining_nodes,
58+
&mut HashSet::new(),
59+
consumer,
60+
);
61+
}
62+
63+
fn bron_kerbosch<N, FN, CO>(
64+
connected: &mut FN,
65+
potential_clique: &HashSet<N>,
66+
remaining_nodes: &mut HashSet<N>,
67+
skip_nodes: &mut HashSet<N>,
68+
consumer: &mut CO,
69+
) where
70+
N: Eq + Hash + Clone,
71+
FN: FnMut(&N, &N) -> bool,
72+
CO: FnMut(&HashSet<N>),
73+
{
74+
if remaining_nodes.is_empty() && skip_nodes.is_empty() {
75+
consumer(potential_clique);
76+
return;
77+
}
78+
let nodes_to_check = remaining_nodes.clone();
79+
for node in &nodes_to_check {
80+
let mut new_potential_clique = potential_clique.clone();
81+
new_potential_clique.insert(node.to_owned());
82+
83+
let mut new_remaining_nodes: HashSet<N> = remaining_nodes
84+
.iter()
85+
.filter(|n| *n != node && connected(node, n))
86+
.cloned()
87+
.collect();
88+
89+
let mut new_skip_list: HashSet<N> = skip_nodes
90+
.iter()
91+
.filter(|n| *n != node && connected(node, n))
92+
.cloned()
93+
.collect();
94+
bron_kerbosch(
95+
connected,
96+
&new_potential_clique,
97+
&mut new_remaining_nodes,
98+
&mut new_skip_list,
99+
consumer,
100+
);
101+
102+
// We're done considering this node. If there was a way to form a clique with it, we
103+
// already discovered its maximal clique in the recursive call above. So, go ahead
104+
// and remove it from the list of remaining nodes and add it to the skip list.
105+
remaining_nodes.remove(node);
106+
skip_nodes.insert(node.to_owned());
107+
}
108+
}

src/undirected/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Algorithms for undirected graphs.
22
3+
pub mod cliques;
34
pub mod connected_components;
45
pub mod kruskal;
56
pub mod prim;

tests/cliques.rs

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use std::collections::HashSet;
2+
3+
use itertools::Itertools;
4+
use pathfinding::prelude::*;
5+
6+
#[test]
7+
fn find_cliques() {
8+
let vertices: Vec<i32> = (1..10).collect_vec();
9+
let cliques = maximal_cliques_collect(&vertices, &mut |a, b| (*a - *b) % 3 == 0);
10+
let cliques_as_vectors: Vec<Vec<i32>> = sort(&cliques);
11+
12+
assert_eq!(
13+
vec![vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9]],
14+
cliques_as_vectors
15+
);
16+
}
17+
18+
#[test]
19+
fn test_same_node_appears_in_multiple_clique() {
20+
let vertices: Vec<i32> = (1..10).collect_vec();
21+
let cliques = maximal_cliques_collect(&vertices, &mut |a, b| {
22+
(*a % 3 == 0) && (*b % 3 == 0) || ((*a - *b) % 4 == 0)
23+
});
24+
let cliques_as_vectors: Vec<Vec<i32>> = sort(&cliques);
25+
26+
assert_eq!(
27+
vec![
28+
vec![1, 5, 9],
29+
vec![2, 6],
30+
vec![3, 6, 9],
31+
vec![3, 7],
32+
vec![4, 8]
33+
],
34+
cliques_as_vectors
35+
);
36+
}
37+
38+
fn sort(cliques: &[HashSet<&i32>]) -> Vec<Vec<i32>> {
39+
let mut cliques_as_vectors: Vec<Vec<i32>> = cliques
40+
.iter()
41+
.map(|cliq| {
42+
let mut s = cliq.iter().map(|&x| *x).collect_vec();
43+
s.sort_unstable();
44+
s
45+
})
46+
.collect();
47+
cliques_as_vectors.sort();
48+
cliques_as_vectors
49+
}

0 commit comments

Comments
 (0)