Skip to content

Commit

Permalink
feat: implement Bron-Kerbosch algorithm
Browse files Browse the repository at this point in the history
to find maximal cliques in a graph.
  • Loading branch information
gzsombor authored and samueltardieu committed Dec 29, 2024
1 parent 50c6c39 commit 5c61369
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
//! - [connected components](undirected/connected_components/index.html): find disjoint connected sets of vertices ([⇒ Wikipedia][Connected components])
//! - [Kruskal](undirected/kruskal/index.html): find a minimum-spanning-tree ([⇒ Wikipedia][Kruskal])
//! - [Prim](undirected/prim/index.html): find a minimum-spanning-tree ([⇒ Wikipedia][Prim])
//! - [cliques]: find maximum cliques in a graph ([= Wikipedia][BronKerbosch])
//!
//! ### Matching
//!
Expand Down Expand Up @@ -80,6 +81,7 @@
//! [A*]: https://en.wikipedia.org/wiki/A*_search_algorithm
//! [BFS]: https://en.wikipedia.org/wiki/Breadth-first_search
//! [Brent]: https://en.wikipedia.org/wiki/Cycle_detection#Brent's_algorithm
//! [BronKerbosch]: https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm
//! [Connected components]: https://en.wikipedia.org/wiki/Connected_component_(graph_theory)
//! [DFS]: https://en.wikipedia.org/wiki/Depth-first_search
//! [Dijkstra]: https://en.wikipedia.org/wiki/Dijkstra's_algorithm
Expand Down Expand Up @@ -131,6 +133,7 @@ pub mod prelude {
pub use crate::grid::*;
pub use crate::kuhn_munkres::*;
pub use crate::matrix::*;
pub use crate::undirected::cliques::*;
pub use crate::undirected::connected_components::*;
pub use crate::undirected::kruskal::*;
pub use crate::utils::*;
Expand Down
108 changes: 108 additions & 0 deletions src/undirected/cliques.rs
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());
}
}
1 change: 1 addition & 0 deletions src/undirected/mod.rs
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;
49 changes: 49 additions & 0 deletions tests/cliques.rs
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
}

0 comments on commit 5c61369

Please sign in to comment.