use std::collections::{HashMap, HashSet};
use super::bfs::Bfs;
use crate::graph::capability::{Bigraph, Directed, InsertEdge, InsertNode, StableNode};
use crate::graph::Graph;
use crate::VecGraph;
pub struct TarjanScc<'r, G: ?Sized, N, Ns> {
graph: &'r G,
index_counter: usize,
scc_stack: Vec<N>,
on_stack: HashSet<N>,
index: HashMap<N, usize>,
lowlink: HashMap<N, usize>,
nodes: Ns,
dfs_stack: Vec<(N, Vec<N>, usize)>,
}
pub fn tarjan_scc<'r, G>(
graph: &'r G,
) -> TarjanScc<'r, G, G::NodeIx, <G as crate::graph::GraphOperation<'r>>::NodeIndices>
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
TarjanScc {
graph,
index_counter: 0,
scc_stack: Vec::new(),
on_stack: HashSet::new(),
index: HashMap::new(),
lowlink: HashMap::new(),
nodes: <_ as crate::graph::GraphOperation<'_>>::node_indices(graph),
dfs_stack: Vec::new(),
}
}
impl<'r, G> Iterator
for TarjanScc<'r, G, G::NodeIx, <G as crate::graph::GraphOperation<'r>>::NodeIndices>
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
type Item = Vec<G::NodeIx>;
fn next(&mut self) -> Option<Vec<G::NodeIx>> {
loop {
if let Some((current, ref succs, ref mut idx)) = self.dfs_stack.last_mut() {
if *idx < succs.len() {
let succ = succs[*idx];
*idx += 1;
let current = *current;
if !self.index.contains_key(&succ) {
self.index.insert(succ, self.index_counter);
self.lowlink.insert(succ, self.index_counter);
self.index_counter += 1;
self.scc_stack.push(succ);
self.on_stack.insert(succ);
let succ_succs: Vec<G::NodeIx> =
unsafe { self.graph.neighbor_indices_from_unchecked(succ) }.collect();
self.dfs_stack.push((succ, succ_succs, 0));
} else if self.on_stack.contains(&succ) {
let succ_index = self.index[&succ];
let ll = self.lowlink.get_mut(¤t).unwrap();
if succ_index < *ll {
*ll = succ_index;
}
}
} else {
let current = *current;
if self.dfs_stack.len() > 1 {
let parent = self.dfs_stack[self.dfs_stack.len() - 2].0;
let current_ll = self.lowlink[¤t];
let parent_ll = self.lowlink.get_mut(&parent).unwrap();
if current_ll < *parent_ll {
*parent_ll = current_ll;
}
}
let is_root = self.lowlink[¤t] == self.index[¤t];
self.dfs_stack.pop();
if is_root {
let mut scc = Vec::new();
loop {
let w = self.scc_stack.pop().unwrap();
self.on_stack.remove(&w);
scc.push(w);
if w == current {
break;
}
}
return Some(scc);
}
}
} else {
loop {
let node = self.nodes.next()?;
if !self.index.contains_key(&node) {
self.index.insert(node, self.index_counter);
self.lowlink.insert(node, self.index_counter);
self.index_counter += 1;
self.scc_stack.push(node);
self.on_stack.insert(node);
let succs: Vec<G::NodeIx> =
unsafe { self.graph.neighbor_indices_from_unchecked(node) }.collect();
self.dfs_stack.push((node, succs, 0));
break;
}
}
}
}
}
}
pub struct KosarajuScc<'r, G: ?Sized, N> {
graph: &'r G,
finish_order: Vec<N>,
finish_idx: usize,
assigned: HashSet<N>,
}
pub fn kosaraju_scc<'r, G>(graph: &'r G) -> KosarajuScc<'r, G, G::NodeIx>
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
let mut visited = HashSet::new();
let mut finish_order = Vec::new();
for node in <_ as crate::graph::GraphOperation<'_>>::node_indices(graph) {
if visited.contains(&node) {
continue;
}
let mut stack: Vec<(G::NodeIx, bool)> = vec![(node, false)];
visited.insert(node);
while let Some((current, expanded)) = stack.last_mut() {
if *expanded {
finish_order.push(*current);
stack.pop();
} else {
*expanded = true;
let current = *current;
let succs: Vec<G::NodeIx> =
unsafe { graph.neighbor_indices_from_unchecked(current) }.collect();
for succ in succs.into_iter().rev() {
if visited.insert(succ) {
stack.push((succ, false));
}
}
}
}
}
finish_order.reverse();
KosarajuScc {
graph,
finish_order,
finish_idx: 0,
assigned: HashSet::new(),
}
}
impl<'r, G> Iterator for KosarajuScc<'r, G, G::NodeIx>
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
type Item = Vec<G::NodeIx>;
fn next(&mut self) -> Option<Vec<G::NodeIx>> {
while self.finish_idx < self.finish_order.len() {
let node = self.finish_order[self.finish_idx];
self.finish_idx += 1;
if self.assigned.contains(&node) {
continue;
}
let mut scc = Vec::new();
let mut stack = vec![node];
self.assigned.insert(node);
while let Some(current) = stack.pop() {
scc.push(current);
let preds: Vec<G::NodeIx> =
unsafe { self.graph.neighbor_indices_to_unchecked(current) }.collect();
for pred in preds {
if self.assigned.insert(pred) {
stack.push(pred);
}
}
}
return Some(scc);
}
None
}
}
pub fn is_cyclic_directed<G>(graph: &G) -> bool
where
G: Graph + for<'a> Directed<'a> + ?Sized,
<G as crate::graph::GraphProperty>::Endpoints: for<'scope> crate::graph::edge::Map<
crate::graph::context::NodeIx<'scope, <G as crate::graph::GraphProperty>::NodeIx>,
>,
{
graph.scope(|ctx| super::toposort::toposort(ctx).is_err())
}
pub fn connected_components<G>(graph: &G) -> usize
where
G: Graph + ?Sized,
{
let mut visited = HashSet::new();
let mut count = 0;
for node in <_ as crate::graph::GraphOperation<'_>>::node_indices(graph) {
if !visited.insert(node) {
continue;
}
count += 1;
let mut stack = vec![node];
while let Some(n) = stack.pop() {
for wi in
unsafe { <G as crate::graph::GraphOperation<'_>>::walks_of_unchecked(graph, n) }
{
let neighbor = wi.into_parts().2;
if visited.insert(neighbor) {
stack.push(neighbor);
}
}
}
}
count
}
pub fn has_path_connecting<'r, G>(graph: &'r G, source: G::NodeIx, target: G::NodeIx) -> bool
where
G: Graph + Directed<'r> + ?Sized,
<G as crate::graph::GraphProperty>::Endpoints: for<'scope> crate::graph::edge::Map<
crate::graph::context::NodeIx<'scope, <G as crate::graph::GraphProperty>::NodeIx>,
Mapped = [crate::graph::context::NodeIx<'scope, <G as crate::graph::GraphProperty>::NodeIx>;
2],
>,
{
assert!(Graph::contains_node_index(graph, target));
if source == target {
return true;
}
unsafe { has_path_connecting_unchecked(graph, source, target) }
}
pub unsafe fn has_path_connecting_unchecked<'r, G>(
graph: &'r G,
source: G::NodeIx,
target: G::NodeIx,
) -> bool
where
G: Graph + Directed<'r> + ?Sized,
<G as crate::graph::GraphProperty>::Endpoints: for<'scope> crate::graph::edge::Map<
crate::graph::context::NodeIx<'scope, <G as crate::graph::GraphProperty>::NodeIx>,
Mapped = [crate::graph::context::NodeIx<'scope, <G as crate::graph::GraphProperty>::NodeIx>;
2],
>,
{
graph.scope(|ctx| {
let source = ctx.wrap_node(source);
let target = ctx.wrap_node(target);
unsafe { Bfs::new_unchecked(ctx, source) }.any(|n| n == target)
})
}
pub fn condensation<'r, G>(graph: &'r G, make_acyclic: bool) -> VecGraph<Vec<G::NodeIx>, ()>
where
G: Graph + Directed<'r> + Bigraph + StableNode + ?Sized,
{
let sccs: Vec<Vec<G::NodeIx>> = tarjan_scc(graph).collect();
let mut node_to_scc: HashMap<G::NodeIx, usize> = HashMap::new();
for (scc_idx, scc) in sccs.iter().enumerate() {
for &node in scc {
node_to_scc.insert(node, scc_idx);
}
}
let mut condensed = VecGraph::<Vec<G::NodeIx>, ()>::default();
let mut scc_node_ids: Vec<u32> = Vec::new();
for scc in &sccs {
let nix =
unsafe { InsertNode::insert_node_unchecked(&mut condensed, scc.clone()) }.unwrap();
scc_node_ids.push(nix);
}
let mut seen_edges: HashSet<(usize, usize)> = HashSet::new();
for eix in <_ as crate::graph::GraphOperation<'_>>::edge_indices(graph) {
let tail = graph.edge_tail_index(eix);
let head = graph.edge_head_index(eix);
let scc_tail = node_to_scc[&tail];
let scc_head = node_to_scc[&head];
if make_acyclic && scc_tail == scc_head {
continue;
}
if seen_edges.insert((scc_tail, scc_head)) {
unsafe {
InsertEdge::insert_edge_unchecked(
&mut condensed,
(),
[scc_node_ids[scc_tail], scc_node_ids[scc_head]],
)
}
.ok();
}
}
condensed
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BTreeGraph;
fn diamond_btree() -> BTreeGraph<u32, &'static str> {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_node(3).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("0->2", [0, 2]).unwrap();
g.insert_edge("1->3", [1, 3]).unwrap();
g.insert_edge("2->3", [2, 3]).unwrap();
g
}
fn cycle_graph() -> BTreeGraph<u32, &'static str> {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("1->2", [1, 2]).unwrap();
g.insert_edge("2->0", [2, 0]).unwrap();
g
}
fn two_scc_graph() -> BTreeGraph<u32, &'static str> {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_node(3).unwrap();
g.insert_node(4).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("1->2", [1, 2]).unwrap();
g.insert_edge("2->0", [2, 0]).unwrap();
g.insert_edge("2->3", [2, 3]).unwrap();
g.insert_edge("3->4", [3, 4]).unwrap();
g.insert_edge("4->3", [4, 3]).unwrap();
g
}
#[test]
fn tarjan_diamond_no_cycles() {
let g = diamond_btree();
let sccs: Vec<_> = tarjan_scc(&g).collect();
assert_eq!(sccs.len(), 4);
for scc in &sccs {
assert_eq!(scc.len(), 1);
}
}
#[test]
fn tarjan_cycle() {
let g = cycle_graph();
let sccs: Vec<_> = tarjan_scc(&g).collect();
assert_eq!(sccs.len(), 1);
assert_eq!(sccs[0].len(), 3);
}
#[test]
fn tarjan_two_sccs() {
let g = two_scc_graph();
let sccs: Vec<_> = tarjan_scc(&g).collect();
assert_eq!(sccs.len(), 2);
let sizes: HashSet<usize> = sccs.iter().map(|s| s.len()).collect();
assert!(sizes.contains(&3));
assert!(sizes.contains(&2));
}
#[test]
fn kosaraju_cycle() {
let g = cycle_graph();
let sccs: Vec<_> = kosaraju_scc(&g).collect();
assert_eq!(sccs.len(), 1);
assert_eq!(sccs[0].len(), 3);
}
#[test]
fn kosaraju_two_sccs() {
let g = two_scc_graph();
let sccs: Vec<_> = kosaraju_scc(&g).collect();
assert_eq!(sccs.len(), 2);
let sizes: HashSet<usize> = sccs.iter().map(|s| s.len()).collect();
assert!(sizes.contains(&3));
assert!(sizes.contains(&2));
}
#[test]
fn is_cyclic_yes() {
let g = cycle_graph();
assert!(is_cyclic_directed(&g));
}
#[test]
fn is_cyclic_no() {
let g = diamond_btree();
assert!(!is_cyclic_directed(&g));
}
#[test]
fn connected_components_connected() {
let g = diamond_btree();
assert_eq!(connected_components(&g), 1);
}
#[test]
fn connected_components_disconnected() {
let mut g = BTreeGraph::<u32, &str>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
assert_eq!(connected_components(&g), 2);
}
#[test]
fn has_path_yes() {
let g = diamond_btree();
assert!(has_path_connecting(&g, 0, 3));
}
#[test]
fn has_path_no() {
let g = diamond_btree();
assert!(!has_path_connecting(&g, 3, 0));
}
#[test]
fn has_path_self() {
let g = diamond_btree();
assert!(has_path_connecting(&g, 0, 0));
}
#[test]
fn condensation_dag() {
let g = diamond_btree();
let condensed = condensation(&g, true);
let nodes: Vec<_> = condensed.nodes().collect();
assert_eq!(nodes.len(), 4);
}
#[test]
fn condensation_two_sccs() {
let g = two_scc_graph();
let condensed = condensation(&g, true);
let nodes: Vec<_> = condensed.nodes().collect();
assert_eq!(nodes.len(), 2);
let edges: Vec<_> = condensed.edges().collect();
assert_eq!(edges.len(), 1);
}
}