gomory_hu_tree/tree.rs
1use std::collections::{HashMap, VecDeque};
2
3/// Represents an edge in the Gomory-Hu tree.
4///
5/// Each edge connects two nodes from the original graph (or supernodes formed during construction)
6/// and stores a capacity, which corresponds to the min-cut value between those two nodes
7/// (or sets of nodes) in the original graph.
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[derive(Debug, Clone, PartialEq)]
10pub struct TreeEdge {
11 /// One endpoint of the tree edge, representing a vertex from the original graph.
12 pub source: usize,
13 /// The other endpoint of the tree edge, representing a vertex from the original graph.
14 pub target: usize,
15 /// The capacity of this tree edge, equal to the min-cut value between `source` and `target`
16 /// (or the components they represented) in the step it was added.
17 pub capacity: f64,
18}
19
20impl TreeEdge {
21 /// Creates a new `TreeEdge`.
22 ///
23 /// # Arguments
24 /// * `source` - The source vertex index.
25 /// * `target` - The target vertex index.
26 /// * `capacity` - The capacity of the edge, representing a min-cut value.
27 pub fn new(source: usize, target: usize, capacity: f64) -> Self {
28 Self {
29 source,
30 target,
31 capacity,
32 }
33 }
34}
35
36/// Represents a Gomory-Hu tree.
37///
38/// The tree stores the all-pairs min-cut information for an undirected graph.
39/// The min-cut value between any two nodes `s` and `t` in the original graph
40/// is equal to the minimum capacity of any edge on the unique path between
41/// `s` and `t` in this Gomory-Hu tree.
42#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43#[derive(Debug, Clone)]
44pub struct GomoryHuTree {
45 /// The number of vertices in the original graph for which this tree was constructed.
46 pub(crate) vertex_count: usize,
47 /// A list of edges that form the Gomory-Hu tree.
48 pub(crate) edges: Vec<TreeEdge>,
49}
50
51impl GomoryHuTree {
52 /// Creates a new `GomoryHuTree` from a list of tree edges and the original vertex count.
53 ///
54 /// This constructor is typically used by tree construction algorithms like `gusfield_tree`.
55 ///
56 /// # Arguments
57 /// * `edges` - A vector of `TreeEdge`s forming the tree.
58 /// * `vertex_count` - The number of vertices in the original graph.
59 pub fn new(edges: Vec<TreeEdge>, vertex_count: usize) -> Self {
60 Self {
61 edges,
62 vertex_count,
63 }
64 }
65
66 /// Calculates the min-cut value between two vertices `s` and `t` from the original graph.
67 ///
68 /// This method finds the unique path between `s` and `t` in the Gomory-Hu tree
69 /// and returns the minimum capacity of an edge on that path.
70 ///
71 /// The current implementation uses a Breadth-First Search (BFS) to find the path
72 /// and its bottleneck capacity, resulting in O(V) query time, where V is the number
73 /// of vertices in the original graph.
74 ///
75 /// # Arguments
76 /// * `s` - The source vertex index.
77 /// * `t` - The target vertex index.
78 ///
79 /// # Returns
80 /// The min-cut value between `s` and `t`. Returns `f64::INFINITY` if `s == t`.
81 /// Returns `0.0` if `s` or `t` are out of bounds after initial checks, or if no path is found
82 /// (though a path should always exist in a valid Gomory-Hu tree for `vertex_count > 0` and `s != t`).
83 ///
84 /// # Panics
85 /// Panics if `s` or `t` are greater than or equal to `vertex_count` at the start of the function.
86 pub fn min_cut_value(&self, s: usize, t: usize) -> f64 {
87 if self.vertex_count == 0 {
88 return 0.0;
89 }
90 if s >= self.vertex_count || t >= self.vertex_count {
91 // This check is important as s and t are used as indices.
92 panic!(
93 "Vertex index out of bounds (s: {}, t: {}, vc: {})",
94 s, t, self.vertex_count
95 );
96 }
97 if s == t {
98 return f64::INFINITY;
99 }
100
101 // Build an adjacency list representation of the tree for pathfinding.
102 // This is done per-query; for higher performance, a persistent adjacency list
103 // or a specialized tree data structure (e.g., for LCA) would be better.
104 let mut adj: HashMap<usize, Vec<(usize, f64)>> = HashMap::new();
105 for edge in &self.edges {
106 adj.entry(edge.source)
107 .or_default()
108 .push((edge.target, edge.capacity));
109 adj.entry(edge.target)
110 .or_default()
111 .push((edge.source, edge.capacity));
112 }
113
114 // BFS to find path from s to t and the minimum capacity on that path.
115 // queue stores (current_node, min_capacity_on_path_from_s_to_current_node, path_for_debug)
116 let mut queue: VecDeque<(usize, f64, Vec<usize>)> = VecDeque::new();
117 queue.push_back((s, f64::INFINITY, vec![s]));
118
119 // visited_bfs prevents cycles and redundant exploration.
120 // Size based on vertex_count from original graph.
121 let mut visited_bfs = vec![false; self.vertex_count];
122 visited_bfs[s] = true; // s is known to be < self.vertex_count due to earlier panic.
123
124 while let Some((curr, path_min_cap, current_path)) = queue.pop_front() {
125 if curr == t {
126 return path_min_cap; // Found path to t, return the bottleneck capacity.
127 }
128
129 if let Some(neighbors) = adj.get(&curr) {
130 for &(neighbor, edge_cap) in neighbors {
131 // Ensure neighbor is a valid index before using it for visited_bfs.
132 if neighbor < self.vertex_count && !visited_bfs[neighbor] {
133 visited_bfs[neighbor] = true;
134 let mut next_path = current_path.clone(); // Clone path for debugging/logging if needed.
135 next_path.push(neighbor);
136 // The new path's bottleneck is the minimum of the current path's bottleneck
137 // and the capacity of the edge just traversed.
138 queue.push_back((neighbor, path_min_cap.min(edge_cap), next_path));
139 }
140 }
141 }
142 }
143
144 // Should ideally not be reached if s and t are in the same connected component of a valid Gomory-Hu tree.
145 // A Gomory-Hu tree for a graph with N > 0 vertices should be connected.
146 // If the original graph was disconnected, gusfield_tree might produce a forest.
147 // In such a case, a query between nodes in different components would find no path.
148 // The min-cut value in a disconnected graph between two nodes in different components is 0.
149 0.0
150 }
151
152 /// Converts the Gomory-Hu tree to DOT format for visualization.
153 ///
154 /// The DOT format can be used with tools like Graphviz to generate images of the tree.
155 /// Each edge in the output will be labeled with its capacity.
156 /// If the tree has no edges but has vertices (e.g. for a 1-node original graph, or a 0-node graph),
157 /// it will represent the nodes without edges.
158 ///
159 /// # Returns
160 /// A string containing the DOT representation of the tree.
161 pub fn to_dot(&self) -> String {
162 let mut dot = String::from("graph GomoryHuTree {\n");
163 if self.vertex_count == 0 {
164 // No nodes to represent for an empty original graph.
165 } else if self.edges.is_empty() {
166 // Graph might have nodes but no edges (e.g., a single node graph, or a graph of isolated nodes).
167 // The Gomory-Hu "tree" for N isolated nodes would be N nodes and 0 edges.
168 // Gusfield's algorithm as implemented should result in N-1 edges if N > 1 and graph is connected.
169 // If N=1, edges list is empty. If N=0, vertex_count is 0.
170 // This ensures all original nodes are listed if there are no tree edges.
171 for i in 0..self.vertex_count {
172 dot.push_str(&format!(" {i}\n"));
173 }
174 } else {
175 for edge in &self.edges {
176 dot.push_str(&format!(
177 " {} -- {} [label=\"{:.2}\"]\n",
178 edge.source, edge.target, edge.capacity
179 ));
180 }
181 }
182 dot.push_str("}\n");
183 dot
184 }
185
186 /// Returns the number of vertices in the original graph for which this tree was constructed.
187 pub fn vertex_count(&self) -> usize {
188 self.vertex_count
189 }
190
191 /// Returns the number of edges in the Gomory-Hu tree.
192 /// For a graph with N > 0 vertices, this should be N-1 if the graph is connected.
193 /// Returns 0 if N=0 or N=1.
194 pub fn edge_count(&self) -> usize {
195 self.edges.len()
196 }
197}