-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement Bron-Kerbosch algorithm
to find maximal cliques in a graph.
- Loading branch information
1 parent
50c6c39
commit 5c61369
Showing
4 changed files
with
161 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
//! Find cliques in an undirected graph. | ||
use std::collections::HashSet; | ||
use std::hash::Hash; | ||
|
||
/// Algorithm for finding all maximal cliques in an undirected graph. | ||
/// That is, it lists all subsets of vertices with the two properties that each pair of vertices in | ||
/// one of the listed subsets is connected by an edge, and no listed subset can have | ||
/// any additional vertices added to it while preserving its complete connectivity. | ||
/// [Bron-Kerbosch algorithm](https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm). | ||
/// | ||
/// | ||
/// - `vertices` is the list of all nodes. | ||
/// - `connected` returns true if the two given node is connected. | ||
/// - return a list of cliques. | ||
pub fn maximal_cliques_collect<N, FN, IN>(vertices: IN, connected: &mut FN) -> Vec<HashSet<N>> | ||
where | ||
N: Eq + Hash + Clone, | ||
FN: FnMut(&N, &N) -> bool, | ||
IN: IntoIterator<Item = N>, | ||
{ | ||
let mut result = Vec::new(); | ||
let mut consumer = |n: &HashSet<N>| result.push(n.to_owned()); | ||
let mut remaining_nodes: HashSet<N> = vertices.into_iter().collect::<HashSet<_>>(); | ||
bron_kerbosch( | ||
connected, | ||
&HashSet::new(), | ||
&mut remaining_nodes, | ||
&mut HashSet::new(), | ||
&mut consumer, | ||
); | ||
result | ||
} | ||
|
||
/// Algorithm for finding all maximal cliques in an undirected graph. | ||
/// That is, it lists all subsets of vertices with the two properties that each pair of vertices in | ||
/// one of the listed subsets is connected by an edge, and no listed subset can have | ||
/// any additional vertices added to it while preserving its complete connectivity. | ||
/// [Bron-Kerbosch algorithm](https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm). | ||
/// | ||
/// | ||
/// - `vertices` is the list of all nodes. | ||
/// - `connected` returns true if the two given node is connected. | ||
/// - 'consumer' function which called for each clique. | ||
/// | ||
pub fn maximal_cliques<N, FN, IN, CO>(vertices: IN, connected: &mut FN, consumer: &mut CO) | ||
where | ||
N: Eq + Hash + Clone, | ||
FN: FnMut(&N, &N) -> bool, | ||
IN: IntoIterator<Item = N>, | ||
CO: FnMut(&HashSet<N>), | ||
{ | ||
let mut remaining_nodes: HashSet<N> = vertices.into_iter().collect(); | ||
bron_kerbosch( | ||
connected, | ||
&HashSet::new(), | ||
&mut remaining_nodes, | ||
&mut HashSet::new(), | ||
consumer, | ||
); | ||
} | ||
|
||
fn bron_kerbosch<N, FN, CO>( | ||
connected: &mut FN, | ||
potential_clique: &HashSet<N>, | ||
remaining_nodes: &mut HashSet<N>, | ||
skip_nodes: &mut HashSet<N>, | ||
consumer: &mut CO, | ||
) where | ||
N: Eq + Hash + Clone, | ||
FN: FnMut(&N, &N) -> bool, | ||
CO: FnMut(&HashSet<N>), | ||
{ | ||
if remaining_nodes.is_empty() && skip_nodes.is_empty() { | ||
consumer(potential_clique); | ||
return; | ||
} | ||
let nodes_to_check = remaining_nodes.clone(); | ||
for node in &nodes_to_check { | ||
let mut new_potential_clique = potential_clique.clone(); | ||
new_potential_clique.insert(node.to_owned()); | ||
|
||
let mut new_remaining_nodes: HashSet<N> = remaining_nodes | ||
.iter() | ||
.filter(|n| *n != node && connected(node, n)) | ||
.cloned() | ||
.collect(); | ||
|
||
let mut new_skip_list: HashSet<N> = skip_nodes | ||
.iter() | ||
.filter(|n| *n != node && connected(node, n)) | ||
.cloned() | ||
.collect(); | ||
bron_kerbosch( | ||
connected, | ||
&new_potential_clique, | ||
&mut new_remaining_nodes, | ||
&mut new_skip_list, | ||
consumer, | ||
); | ||
|
||
// We're done considering this node. If there was a way to form a clique with it, we | ||
// already discovered its maximal clique in the recursive call above. So, go ahead | ||
// and remove it from the list of remaining nodes and add it to the skip list. | ||
remaining_nodes.remove(node); | ||
skip_nodes.insert(node.to_owned()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
//! Algorithms for undirected graphs. | ||
pub mod cliques; | ||
pub mod connected_components; | ||
pub mod kruskal; | ||
pub mod prim; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
use std::collections::HashSet; | ||
|
||
use itertools::Itertools; | ||
use pathfinding::prelude::*; | ||
|
||
#[test] | ||
fn find_cliques() { | ||
let vertices: Vec<i32> = (1..10).collect_vec(); | ||
let cliques = maximal_cliques_collect(&vertices, &mut |a, b| (*a - *b) % 3 == 0); | ||
let cliques_as_vectors: Vec<Vec<i32>> = sort(&cliques); | ||
|
||
assert_eq!( | ||
vec![vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9]], | ||
cliques_as_vectors | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_same_node_appears_in_multiple_clique() { | ||
let vertices: Vec<i32> = (1..10).collect_vec(); | ||
let cliques = maximal_cliques_collect(&vertices, &mut |a, b| { | ||
(*a % 3 == 0) && (*b % 3 == 0) || ((*a - *b) % 4 == 0) | ||
}); | ||
let cliques_as_vectors: Vec<Vec<i32>> = sort(&cliques); | ||
|
||
assert_eq!( | ||
vec![ | ||
vec![1, 5, 9], | ||
vec![2, 6], | ||
vec![3, 6, 9], | ||
vec![3, 7], | ||
vec![4, 8] | ||
], | ||
cliques_as_vectors | ||
); | ||
} | ||
|
||
fn sort(cliques: &[HashSet<&i32>]) -> Vec<Vec<i32>> { | ||
let mut cliques_as_vectors: Vec<Vec<i32>> = cliques | ||
.iter() | ||
.map(|cliq| { | ||
let mut s = cliq.iter().map(|&x| *x).collect_vec(); | ||
s.sort_unstable(); | ||
s | ||
}) | ||
.collect(); | ||
cliques_as_vectors.sort(); | ||
cliques_as_vectors | ||
} |