use alloc::{vec, vec::Vec};
use core::{cmp::min, hash::Hash};
use fixedbitset::FixedBitSet;
use hashbrown::{HashMap, HashSet};
use crate::visit;
use crate::visit::{EdgeRef, IntoEdges, IntoNodeReferences, NodeIndexable, NodeRef};
pub fn articulation_points<G>(g: G) -> HashSet<G::NodeId>
where
G: IntoNodeReferences + IntoEdges + NodeIndexable + visit::GraphProp,
G::NodeWeight: Clone,
G::EdgeWeight: Clone + PartialOrd,
G::NodeId: Eq + Hash,
{
let graph_size = g.node_references().size_hint().0;
let mut auxiliary_const = ArticulationPointTracker::new(graph_size);
for node in g.node_references() {
let node_id = g.to_index(node.id());
if !auxiliary_const.visited[node_id] {
_dfs(&g, node_id, &mut auxiliary_const);
}
}
auxiliary_const
.articulation_points
.into_iter()
.map(|id| g.from_index(id))
.collect()
}
enum RecursionStep {
BaseStep(usize),
ProcessChildStep(usize, usize),
NoBackEdgeConditionCheck(usize, usize),
RootMoreThanTwoChildrenCheck(usize),
}
struct ArticulationPointTracker {
visited: FixedBitSet,
low: Vec<usize>,
disc: Vec<usize>,
parent: Vec<usize>,
time: usize,
articulation_points: HashSet<usize>,
}
impl ArticulationPointTracker {
fn new(graph_size: usize) -> Self {
Self {
visited: FixedBitSet::with_capacity(graph_size),
low: vec![usize::MAX; graph_size],
disc: vec![usize::MAX; graph_size],
parent: vec![usize::MAX; graph_size],
articulation_points: HashSet::with_capacity(graph_size),
time: 0,
}
}
}
fn _dfs<G>(g: &G, target_node: usize, articulation_point_tracker: &mut ArticulationPointTracker)
where
G: IntoEdges + NodeIndexable,
{
let mut stack: Vec<RecursionStep> = vec![RecursionStep::BaseStep(target_node)];
let mut children_count: HashMap<usize, usize> = HashMap::new();
while let Some(recursion_step) = stack.pop() {
match recursion_step {
RecursionStep::BaseStep(current_node) => {
articulation_point_tracker.visited.insert(current_node);
articulation_point_tracker.disc[current_node] = articulation_point_tracker.time;
articulation_point_tracker.low[current_node] = articulation_point_tracker.time;
articulation_point_tracker.time += 1;
stack.push(RecursionStep::RootMoreThanTwoChildrenCheck(current_node));
for edge in g.edges(g.from_index(current_node)) {
let child_node = g.to_index(edge.target());
stack.push(RecursionStep::ProcessChildStep(current_node, child_node));
}
}
RecursionStep::ProcessChildStep(current_node, child_node) => {
if !articulation_point_tracker.visited.contains(child_node) {
articulation_point_tracker.parent[child_node] = current_node;
children_count
.entry(current_node)
.and_modify(|c| *c += 1)
.or_insert(1);
stack.push(RecursionStep::NoBackEdgeConditionCheck(
current_node,
child_node,
));
stack.push(RecursionStep::BaseStep(child_node));
} else if child_node != articulation_point_tracker.parent[current_node] {
articulation_point_tracker.low[current_node] = min(
articulation_point_tracker.low[current_node],
articulation_point_tracker.disc[child_node],
);
}
}
RecursionStep::NoBackEdgeConditionCheck(current_node, child_node) => {
articulation_point_tracker.low[current_node] = min(
articulation_point_tracker.low[current_node],
articulation_point_tracker.low[child_node],
);
if articulation_point_tracker.parent[current_node] != usize::MAX
&& articulation_point_tracker.low[child_node]
>= articulation_point_tracker.disc[current_node]
{
articulation_point_tracker
.articulation_points
.insert(current_node);
}
}
RecursionStep::RootMoreThanTwoChildrenCheck(current_node) => {
let child_count = children_count.get(¤t_node).cloned().unwrap_or(0);
if articulation_point_tracker.parent[current_node] == usize::MAX && child_count > 1
{
articulation_point_tracker
.articulation_points
.insert(current_node);
}
}
}
}
}