graph_flow/min_cut.rs
1//! Minimum s-t cut from a completed max-flow computation.
2//!
3//! By the **Max-Flow Min-Cut theorem**, the value of the maximum flow from
4//! source `s` to sink `t` equals the capacity of the minimum cut separating
5//! `s` from `t`. After running a max-flow algorithm, the min-cut can be read
6//! directly from the residual graph in O(V + E).
7
8use crate::FlowGraph;
9use std::collections::VecDeque;
10
11/// The result of a minimum s-t cut computation.
12///
13/// The cut partitions the graph's nodes into two sets: `source_side` (nodes
14/// reachable from the source in the residual graph) and `sink_side` (all
15/// other nodes). Every edge crossing from `source_side` to `sink_side` in the
16/// **original** graph is a cut edge; their capacity sum equals the max flow.
17#[derive(Debug, Clone)]
18pub struct MinCut {
19 /// Nodes reachable from the source in the residual graph after max-flow.
20 ///
21 /// This is the source partition `S` of the min-cut `(S, T)`.
22 pub source_side: Vec<usize>,
23 /// Nodes **not** reachable from the source in the residual graph.
24 ///
25 /// This is the sink partition `T` of the min-cut `(S, T)`.
26 pub sink_side: Vec<usize>,
27 /// The cut edges: `(u, v)` pairs where `u ∈ source_side`, `v ∈ sink_side`,
28 /// and there is a forward edge `u → v` in the original graph.
29 pub cut_edges: Vec<(usize, usize)>,
30 /// Total capacity of all cut edges. Equals the maximum flow value by the
31 /// Max-Flow Min-Cut theorem.
32 pub capacity: f64,
33}
34
35/// Computes the minimum s-t cut from a [`FlowGraph`] on which a max-flow
36/// algorithm has already been run.
37///
38/// The function does **not** run max-flow itself — call [`edmonds_karp`] or
39/// [`ford_fulkerson`] first, then pass the same graph here.
40///
41/// # How it works
42///
43/// After max-flow saturates the network, BFS from `source` in the **residual**
44/// graph (only traversing edges with positive remaining capacity) discovers all
45/// nodes still reachable from `source`. These form the source side `S` of the
46/// cut. Every original edge crossing from `S` to `T = V \ S` is a cut edge.
47///
48/// # Arguments
49///
50/// - `graph` — a [`FlowGraph`] after max-flow has been computed.
51/// - `source` — the source node index.
52///
53/// # Returns
54///
55/// A [`MinCut`] describing the partition and cut edges.
56///
57/// # Complexity
58///
59/// O(V + E).
60///
61/// # Examples
62///
63/// ```
64/// use graph_flow::{FlowGraph, edmonds_karp, min_cut};
65///
66/// let mut g = FlowGraph::new(4);
67/// g.add_edge(0, 1, 10.0);
68/// g.add_edge(0, 2, 5.0);
69/// g.add_edge(1, 3, 10.0);
70/// g.add_edge(2, 3, 5.0);
71///
72/// let max_flow = edmonds_karp(&mut g, 0, 3);
73/// let cut = min_cut(&g, 0);
74///
75/// // Max-flow = min-cut capacity (Max-Flow Min-Cut theorem).
76/// assert_eq!(max_flow, cut.capacity);
77/// assert_eq!(cut.capacity, 15.0);
78/// ```
79///
80/// [`edmonds_karp`]: crate::edmonds_karp::edmonds_karp
81/// [`ford_fulkerson`]: crate::ford_fulkerson::ford_fulkerson
82pub fn min_cut(graph: &FlowGraph, source: usize) -> MinCut {
83 // BFS over the residual graph to find all nodes reachable from source.
84 let reachable = residual_reachable(graph, source);
85
86 let mut source_side = Vec::new();
87 let mut sink_side = Vec::new();
88
89 for (node, &is_reachable) in reachable.iter().enumerate() {
90 if is_reachable {
91 source_side.push(node);
92 } else {
93 sink_side.push(node);
94 }
95 }
96
97 // Collect cut edges and sum their original capacities.
98 let mut cut_edges = Vec::new();
99 let mut capacity = 0.0f64;
100
101 for &u in &source_side {
102 for edge in &graph.adjacency[u] {
103 // A cut edge goes from source_side to sink_side and has positive
104 // original capacity (not a reverse/residual-only edge).
105 if !reachable[edge.to] && edge.capacity > 0.0 {
106 cut_edges.push((u, edge.to));
107 capacity += edge.capacity;
108 }
109 }
110 }
111
112 MinCut {
113 source_side,
114 sink_side,
115 cut_edges,
116 capacity,
117 }
118}
119
120/// BFS over the residual graph (only edges with `residual() > 0`).
121///
122/// Returns a boolean array where `reachable[v]` is `true` iff `v` is
123/// reachable from `source` via edges with positive residual capacity.
124fn residual_reachable(graph: &FlowGraph, source: usize) -> Vec<bool> {
125 let n = graph.node_count();
126 let mut reachable = vec![false; n];
127 let mut queue = VecDeque::new();
128
129 reachable[source] = true;
130 queue.push_back(source);
131
132 while let Some(node) = queue.pop_front() {
133 for edge in &graph.adjacency[node] {
134 if !reachable[edge.to] && edge.residual() > 0.0 {
135 reachable[edge.to] = true;
136 queue.push_back(edge.to);
137 }
138 }
139 }
140
141 reachable
142}