graph_spanning/bridges.rs
1use graph_core::{Graph, NodeId};
2use std::collections::HashMap;
3
4/// Finds all **bridges** in an undirected graph using Tarjan's algorithm.
5///
6/// A bridge (also called a cut edge) is an edge whose removal increases the
7/// number of connected components — i.e., it is the only path between some
8/// pair of nodes.
9///
10/// # Algorithm
11///
12/// DFS assigns each node a **discovery time** (`disc`). The **low-link value**
13/// (`low[u]`) is the smallest discovery time reachable from the subtree rooted
14/// at `u` via back-edges. An edge `(u, v)` is a bridge iff `low[v] > disc[u]`:
15/// the subtree at `v` cannot reach `u` or any earlier node without crossing
16/// this edge.
17///
18/// # Returns
19///
20/// A `Vec` of bridge edges `(source, target)`. For undirected graphs each
21/// bridge is returned once (in the direction it was discovered by DFS).
22///
23/// # Complexity
24///
25/// O(V + E).
26///
27/// # Examples
28///
29/// ```
30/// use graph_core::{AdjacencyList, Graph};
31/// use graph_spanning::bridges;
32///
33/// // Graph: 0-1-2-3, with extra edge 0-2 (making 0-1-2 a cycle).
34/// // The only bridge is 2-3.
35/// let mut g: AdjacencyList<()> = AdjacencyList::undirected();
36/// let n: Vec<_> = (0..4).map(|_| g.add_node(())).collect();
37/// g.add_edge(n[0], n[1], 1.0).unwrap();
38/// g.add_edge(n[1], n[2], 1.0).unwrap();
39/// g.add_edge(n[0], n[2], 1.0).unwrap(); // back-edge, closes a cycle
40/// g.add_edge(n[2], n[3], 1.0).unwrap(); // bridge
41///
42/// let b = bridges(&g);
43/// assert_eq!(b.len(), 1);
44/// assert!((b[0] == (n[2], n[3])) || (b[0] == (n[3], n[2])));
45/// ```
46pub fn bridges<G>(graph: &G) -> Vec<(NodeId, NodeId)>
47where
48 G: Graph<Weight = f64>,
49{
50 let mut state = BridgeState {
51 disc: HashMap::new(),
52 low: HashMap::new(),
53 timer: 0,
54 result: Vec::new(),
55 };
56
57 for node in graph.nodes() {
58 if !state.disc.contains_key(&node) {
59 dfs_bridge(graph, node, None, &mut state);
60 }
61 }
62
63 state.result
64}
65
66struct BridgeState {
67 disc: HashMap<NodeId, usize>,
68 low: HashMap<NodeId, usize>,
69 timer: usize,
70 result: Vec<(NodeId, NodeId)>,
71}
72
73fn dfs_bridge<G>(graph: &G, node: NodeId, parent: Option<NodeId>, state: &mut BridgeState)
74where
75 G: Graph<Weight = f64>,
76{
77 state.disc.insert(node, state.timer);
78 state.low.insert(node, state.timer);
79 state.timer += 1;
80
81 for (neighbour, _) in graph.neighbors(node) {
82 if !state.disc.contains_key(&neighbour) {
83 // Tree edge: recurse.
84 dfs_bridge(graph, neighbour, Some(node), state);
85
86 // Update low[node] from the subtree.
87 let child_low = state.low[&neighbour];
88 let node_low = state.low.get_mut(&node).unwrap();
89 if child_low < *node_low {
90 *node_low = child_low;
91 }
92
93 // Bridge condition: subtree at neighbour cannot reach node or earlier.
94 if state.low[&neighbour] > state.disc[&node] {
95 state.result.push((node, neighbour));
96 }
97 } else if Some(neighbour) != parent {
98 // Back edge: update low[node].
99 let neighbour_disc = state.disc[&neighbour];
100 let node_low = state.low.get_mut(&node).unwrap();
101 if neighbour_disc < *node_low {
102 *node_low = neighbour_disc;
103 }
104 }
105 }
106}
107
108/// Returns true if the graph has no bridges (is 2-edge-connected).
109///
110/// # Examples
111///
112/// ```
113/// use graph_core::{AdjacencyList, Graph};
114/// use graph_spanning::bridges::is_two_edge_connected;
115///
116/// // Complete graph K4 has no bridges.
117/// let mut g: AdjacencyList<()> = AdjacencyList::undirected();
118/// let n: Vec<_> = (0..4).map(|_| g.add_node(())).collect();
119/// for i in 0..4 {
120/// for j in i+1..4 {
121/// g.add_edge(n[i], n[j], 1.0).unwrap();
122/// }
123/// }
124/// assert!(is_two_edge_connected(&g));
125/// ```
126pub fn is_two_edge_connected<G>(graph: &G) -> bool
127where
128 G: Graph<Weight = f64>,
129{
130 graph.node_count() > 0 && bridges(graph).is_empty()
131}