|
1 | 1 | use super::super::indexed_vec::IndexVec;
|
2 |
| -use super::{DirectedGraph, WithNumNodes, WithSuccessors}; |
| 2 | +use super::{DirectedGraph, WithNumNodes, WithSuccessors, WithStartNode}; |
3 | 3 | use crate::bit_set::BitSet;
|
4 | 4 |
|
5 | 5 | #[cfg(test)]
|
@@ -85,3 +85,205 @@ where
|
85 | 85 | Some(n)
|
86 | 86 | }
|
87 | 87 | }
|
| 88 | + |
| 89 | +/// Allows searches to terminate early with a value. |
| 90 | +#[derive(Clone, Copy, Debug)] |
| 91 | +pub enum ControlFlow<T> { |
| 92 | + Break(T), |
| 93 | + Continue, |
| 94 | +} |
| 95 | + |
| 96 | +/// The status of a node in the depth-first search. |
| 97 | +/// |
| 98 | +/// See the documentation of `TriColorDepthFirstSearch` to see how a node's status is updated |
| 99 | +/// during DFS. |
| 100 | +#[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 101 | +pub enum NodeStatus { |
| 102 | + /// This node has been examined by the depth-first search but is not yet `Settled`. |
| 103 | + /// |
| 104 | + /// Also referred to as "gray" or "discovered" nodes in [CLR][]. |
| 105 | + /// |
| 106 | + /// [CLR]: https://en.wikipedia.org/wiki/Introduction_to_Algorithms |
| 107 | + Visited, |
| 108 | + |
| 109 | + /// This node and all nodes reachable from it have been examined by the depth-first search. |
| 110 | + /// |
| 111 | + /// Also referred to as "black" or "finished" nodes in [CLR][]. |
| 112 | + /// |
| 113 | + /// [CLR]: https://en.wikipedia.org/wiki/Introduction_to_Algorithms |
| 114 | + Settled, |
| 115 | +} |
| 116 | + |
| 117 | +struct Event<N> { |
| 118 | + node: N, |
| 119 | + becomes: NodeStatus, |
| 120 | +} |
| 121 | + |
| 122 | +/// A depth-first search that also tracks when all successors of a node have been examined. |
| 123 | +/// |
| 124 | +/// This is based on the DFS described in [Introduction to Algorithms (1st ed.)][CLR], hereby |
| 125 | +/// referred to as **CLR**. However, we use the terminology in [`NodeStatus`][] above instead of |
| 126 | +/// "discovered"/"finished" or "white"/"grey"/"black". Each node begins the search with no status, |
| 127 | +/// becomes `Visited` when it is first examined by the DFS and is `Settled` when all nodes |
| 128 | +/// reachable from it have been examined. This allows us to differentiate between "tree", "back" |
| 129 | +/// and "forward" edges (see [`TriColorVisitor::node_examined`]). |
| 130 | +/// |
| 131 | +/// Unlike the pseudocode in [CLR][], this implementation is iterative and does not use timestamps. |
| 132 | +/// We accomplish this by storing `Event`s on the stack that result in a (possible) state change |
| 133 | +/// for each node. A `Visited` event signifies that we should examine this node if it has not yet |
| 134 | +/// been `Visited` or `Settled`. When a node is examined for the first time, we mark it as |
| 135 | +/// `Visited` and push a `Settled` event for it on stack followed by `Visited` events for all of |
| 136 | +/// its predecessors, scheduling them for examination. Multiple `Visited` events for a single node |
| 137 | +/// may exist on the stack simultaneously if a node has multiple predecessors, but only one |
| 138 | +/// `Settled` event will ever be created for each node. After all `Visited` events for a node's |
| 139 | +/// successors have been popped off the stack (as well as any new events triggered by visiting |
| 140 | +/// those successors), we will pop off that node's `Settled` event. |
| 141 | +/// |
| 142 | +/// [CLR]: https://en.wikipedia.org/wiki/Introduction_to_Algorithms |
| 143 | +/// [`NodeStatus`]: ./enum.NodeStatus.html |
| 144 | +/// [`TriColorVisitor::node_examined`]: ./trait.TriColorVisitor.html#method.node_examined |
| 145 | +pub struct TriColorDepthFirstSearch<'graph, G> |
| 146 | +where |
| 147 | + G: ?Sized + DirectedGraph + WithNumNodes + WithSuccessors, |
| 148 | +{ |
| 149 | + graph: &'graph G, |
| 150 | + stack: Vec<Event<G::Node>>, |
| 151 | + visited: BitSet<G::Node>, |
| 152 | + settled: BitSet<G::Node>, |
| 153 | +} |
| 154 | + |
| 155 | +impl<G> TriColorDepthFirstSearch<'graph, G> |
| 156 | +where |
| 157 | + G: ?Sized + DirectedGraph + WithNumNodes + WithSuccessors, |
| 158 | +{ |
| 159 | + pub fn new(graph: &'graph G) -> Self { |
| 160 | + TriColorDepthFirstSearch { |
| 161 | + graph, |
| 162 | + stack: vec![], |
| 163 | + visited: BitSet::new_empty(graph.num_nodes()), |
| 164 | + settled: BitSet::new_empty(graph.num_nodes()), |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + /// Performs a depth-first search, starting from the given `root`. |
| 169 | + /// |
| 170 | + /// This won't visit nodes that are not reachable from `root`. |
| 171 | + pub fn run_from<V>(mut self, root: G::Node, visitor: &mut V) -> Option<V::BreakVal> |
| 172 | + where |
| 173 | + V: TriColorVisitor<G>, |
| 174 | + { |
| 175 | + use NodeStatus::{Visited, Settled}; |
| 176 | + |
| 177 | + self.stack.push(Event { node: root, becomes: Visited }); |
| 178 | + |
| 179 | + loop { |
| 180 | + match self.stack.pop()? { |
| 181 | + Event { node, becomes: Settled } => { |
| 182 | + let not_previously_settled = self.settled.insert(node); |
| 183 | + assert!(not_previously_settled, "A node should be settled exactly once"); |
| 184 | + if let ControlFlow::Break(val) = visitor.node_settled(node) { |
| 185 | + return Some(val); |
| 186 | + } |
| 187 | + } |
| 188 | + |
| 189 | + Event { node, becomes: Visited } => { |
| 190 | + let not_previously_visited = self.visited.insert(node); |
| 191 | + let prior_status = if not_previously_visited { |
| 192 | + None |
| 193 | + } else if self.settled.contains(node) { |
| 194 | + Some(Settled) |
| 195 | + } else { |
| 196 | + Some(Visited) |
| 197 | + }; |
| 198 | + |
| 199 | + if let ControlFlow::Break(val) = visitor.node_examined(node, prior_status) { |
| 200 | + return Some(val); |
| 201 | + } |
| 202 | + |
| 203 | + // If this node has already been examined, we are done. |
| 204 | + if prior_status.is_some() { |
| 205 | + continue; |
| 206 | + } |
| 207 | + |
| 208 | + // Otherwise, push a `Settled` event for this node onto the stack, then |
| 209 | + // schedule its successors for examination. |
| 210 | + self.stack.push(Event { node, becomes: Settled }); |
| 211 | + for succ in self.graph.successors(node) { |
| 212 | + self.stack.push(Event { node: succ, becomes: Visited }); |
| 213 | + } |
| 214 | + } |
| 215 | + } |
| 216 | + } |
| 217 | + } |
| 218 | +} |
| 219 | + |
| 220 | +impl<G> TriColorDepthFirstSearch<'graph, G> |
| 221 | +where |
| 222 | + G: ?Sized + DirectedGraph + WithNumNodes + WithSuccessors + WithStartNode, |
| 223 | +{ |
| 224 | + /// Performs a depth-first search, starting from `G::start_node()`. |
| 225 | + /// |
| 226 | + /// This won't visit nodes that are not reachable from the start node. |
| 227 | + pub fn run_from_start<V>(self, visitor: &mut V) -> Option<V::BreakVal> |
| 228 | + where |
| 229 | + V: TriColorVisitor<G>, |
| 230 | + { |
| 231 | + let root = self.graph.start_node(); |
| 232 | + self.run_from(root, visitor) |
| 233 | + } |
| 234 | +} |
| 235 | + |
| 236 | +/// What to do when a node is examined or becomes `Settled` during DFS. |
| 237 | +pub trait TriColorVisitor<G> |
| 238 | +where |
| 239 | + G: ?Sized + DirectedGraph, |
| 240 | +{ |
| 241 | + /// The value returned by this search. |
| 242 | + type BreakVal; |
| 243 | + |
| 244 | + /// Called when a node is examined by the depth-first search. |
| 245 | + /// |
| 246 | + /// By checking the value of `prior_status`, this visitor can determine whether the edge |
| 247 | + /// leading to this node was a tree edge (`None`), forward edge (`Some(Settled)`) or back edge |
| 248 | + /// (`Some(Visited)`). For a full explanation of each edge type, see the "Depth-first Search" |
| 249 | + /// chapter in [CLR][] or [wikipedia][]. |
| 250 | + /// |
| 251 | + /// If you want to know *both* nodes linked by each edge, you'll need to modify |
| 252 | + /// `TriColorDepthFirstSearch` to store a `source` node for each `Visited` event. |
| 253 | + /// |
| 254 | + /// [wikipedia]: https://en.wikipedia.org/wiki/Depth-first_search#Output_of_a_depth-first_search |
| 255 | + /// [CLR]: https://en.wikipedia.org/wiki/Introduction_to_Algorithms |
| 256 | + fn node_examined( |
| 257 | + &mut self, |
| 258 | + _target: G::Node, |
| 259 | + _prior_status: Option<NodeStatus>, |
| 260 | + ) -> ControlFlow<Self::BreakVal> { |
| 261 | + ControlFlow::Continue |
| 262 | + } |
| 263 | + |
| 264 | + /// Called after all nodes reachable from this one have been examined. |
| 265 | + fn node_settled(&mut self, _target: G::Node) -> ControlFlow<Self::BreakVal> { |
| 266 | + ControlFlow::Continue |
| 267 | + } |
| 268 | +} |
| 269 | + |
| 270 | +/// This `TriColorVisitor` looks for back edges in a graph, which indicate that a cycle exists. |
| 271 | +pub struct CycleDetector; |
| 272 | + |
| 273 | +impl<G> TriColorVisitor<G> for CycleDetector |
| 274 | +where |
| 275 | + G: ?Sized + DirectedGraph, |
| 276 | +{ |
| 277 | + type BreakVal = (); |
| 278 | + |
| 279 | + fn node_examined( |
| 280 | + &mut self, |
| 281 | + _node: G::Node, |
| 282 | + prior_status: Option<NodeStatus>, |
| 283 | + ) -> ControlFlow<Self::BreakVal> { |
| 284 | + match prior_status { |
| 285 | + Some(NodeStatus::Visited) => ControlFlow::Break(()), |
| 286 | + _ => ControlFlow::Continue, |
| 287 | + } |
| 288 | + } |
| 289 | +} |
0 commit comments