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}