blob: 0ed783261780fb5c49560bbb78330838fc384cdd [file] [log] [blame]
use petgraph_core::deprecated::visit::{
EdgeRef, IntoEdgeReferences, NodeCompactIndexable, NodeRef,
};
use crate::utilities::union_find::UnionFind;
/// \[Generic\] Return the number of connected components of the graph.
///
/// For a directed graph, this is the *weakly* connected components.
/// # Example
/// ```rust
/// use petgraph_algorithms::components::connected_components;
/// use petgraph_core::edge::Directed;
/// use petgraph_graph::Graph;
///
/// let mut graph: Graph<(), (), Directed> = Graph::new();
/// let a = graph.add_node(()); // node with no weight
/// let b = graph.add_node(());
/// let c = graph.add_node(());
/// let d = graph.add_node(());
/// let e = graph.add_node(());
/// let f = graph.add_node(());
/// let g = graph.add_node(());
/// let h = graph.add_node(());
///
/// graph.extend_with_edges(&[
/// (a, b),
/// (b, c),
/// (c, d),
/// (d, a),
/// (e, f),
/// (f, g),
/// (g, h),
/// (h, e),
/// ]);
/// // a ----> b e ----> f
/// // ^ | ^ |
/// // | v | v
/// // d <---- c h <---- g
///
/// assert_eq!(connected_components(&graph), 2);
/// graph.add_edge(b, e, ());
/// assert_eq!(connected_components(&graph), 1);
/// ```
pub fn connected_components<G>(g: G) -> usize
where
G: NodeCompactIndexable + IntoEdgeReferences,
{
let mut vertex_sets = UnionFind::new(g.node_bound());
for edge in g.edge_references() {
let (a, b) = (edge.source(), edge.target());
// union the two vertices of the edge
vertex_sets.union(g.to_index(a), g.to_index(b));
}
let mut labels = vertex_sets.into_labeling();
labels.sort_unstable();
labels.dedup();
labels.len()
}
#[cfg(test)]
mod tests {
use alloc::format;
use petgraph_core::edge::Directed;
use petgraph_graph::Graph;
use proptest::prelude::*;
use crate::components::kosaraju_scc;
#[test]
fn link() {
let mut graph = Graph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
assert_eq!(super::connected_components(&graph), 2);
graph.add_edge(a, b, "A → B");
assert_eq!(super::connected_components(&graph), 1);
graph.add_edge(b, a, "B → A");
assert_eq!(super::connected_components(&graph), 1);
}
#[test]
fn triangle() {
let mut graph = Graph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
assert_eq!(super::connected_components(&graph), 3);
graph.add_edge(a, b, "A → B");
graph.add_edge(b, c, "B → C");
graph.add_edge(c, a, "C → A");
assert_eq!(super::connected_components(&graph), 1);
}
#[test]
fn square() {
let mut graph = Graph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
let d = graph.add_node("D");
assert_eq!(super::connected_components(&graph), 4);
// A → B
//
// C → D
graph.add_edge(a, b, "A → B");
graph.add_edge(c, d, "C → D");
assert_eq!(super::connected_components(&graph), 2);
// A → B
// ↑ ↓
// D ← C
graph.add_edge(b, c, "B → C");
graph.add_edge(d, a, "D → A");
assert_eq!(super::connected_components(&graph), 1);
// also a strongly connected component
assert_eq!(kosaraju_scc(&graph).len(), 1);
}
#[test]
fn wcc_but_not_scc() {
let mut graph = Graph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
let d = graph.add_node("D");
assert_eq!(super::connected_components(&graph), 4);
// A → B
// ↓
// D ← C
graph.add_edge(a, b, "A → B");
graph.add_edge(c, d, "C → D");
graph.add_edge(b, c, "B → C");
assert_eq!(super::connected_components(&graph), 1);
// but not a strongly connected component
assert_eq!(kosaraju_scc(&graph).len(), 4);
}
#[cfg(not(miri))]
proptest! {
#[test]
fn invariant_over_direction(directed in any::<Graph<(), (), Directed, u8>>()) {
let undirected = directed.clone().into_edge_type::<petgraph_core::edge::Undirected>();
prop_assert_eq!(
super::connected_components(&directed),
super::connected_components(&undirected)
);
}
}
}