ruvector_mincut/algorithm/
replacement.rs

1//! Replacement edge data structure for O(log n) reconnection
2//!
3//! Provides fast lookup of replacement edges when a tree edge is deleted.
4//! Based on the level-based approach from dynamic connectivity literature.
5
6use crate::graph::VertexId;
7use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
8
9/// Edge identifier as (smaller, larger) vertex pair
10pub type EdgeKey = (VertexId, VertexId);
11
12/// Normalize an edge to (min, max) ordering
13#[inline]
14fn normalize_edge(u: VertexId, v: VertexId) -> EdgeKey {
15    if u < v {
16        (u, v)
17    } else {
18        (v, u)
19    }
20}
21
22/// Level-based replacement edge index for O(log n) lookup
23///
24/// Organizes non-tree edges by level, enabling efficient replacement
25/// edge discovery when tree edges are deleted.
26///
27/// # Complexity
28/// - Lookup: O(log n) amortized
29/// - Insert: O(log n)
30/// - Delete: O(log n)
31#[derive(Debug, Clone)]
32pub struct ReplacementEdgeIndex {
33    /// Maximum level (log₂ n)
34    max_level: usize,
35    /// Non-tree edges organized by level
36    /// Level 0 contains all non-tree edges initially
37    level_edges: Vec<BTreeSet<EdgeKey>>,
38    /// Reverse lookup: edge -> level
39    edge_level: HashMap<EdgeKey, usize>,
40    /// Tree edges (for quick membership check)
41    tree_edges: HashSet<EdgeKey>,
42    /// Adjacency for each vertex at each level
43    level_adjacency: Vec<HashMap<VertexId, BTreeSet<VertexId>>>,
44    /// Component sizes (for smaller-to-larger promotion)
45    component_size: HashMap<VertexId, usize>,
46}
47
48impl ReplacementEdgeIndex {
49    /// Create a new replacement edge index
50    pub fn new(n: usize) -> Self {
51        // log₂(n) levels
52        let max_level = (n as f64).log2().ceil() as usize + 1;
53        let max_level = max_level.max(1);
54
55        Self {
56            max_level,
57            level_edges: vec![BTreeSet::new(); max_level],
58            edge_level: HashMap::new(),
59            tree_edges: HashSet::new(),
60            level_adjacency: vec![HashMap::new(); max_level],
61            component_size: HashMap::new(),
62        }
63    }
64
65    /// Add a tree edge
66    pub fn add_tree_edge(&mut self, u: VertexId, v: VertexId) {
67        let key = normalize_edge(u, v);
68        self.tree_edges.insert(key);
69    }
70
71    /// Remove a tree edge (returns if it was present)
72    pub fn remove_tree_edge(&mut self, u: VertexId, v: VertexId) -> bool {
73        let key = normalize_edge(u, v);
74        self.tree_edges.remove(&key)
75    }
76
77    /// Add a non-tree edge at level 0
78    pub fn add_non_tree_edge(&mut self, u: VertexId, v: VertexId) {
79        let key = normalize_edge(u, v);
80
81        // Don't add if it's a tree edge
82        if self.tree_edges.contains(&key) {
83            return;
84        }
85
86        // Add at level 0
87        if self.level_edges[0].insert(key) {
88            self.edge_level.insert(key, 0);
89
90            // Update adjacency
91            self.level_adjacency[0].entry(u).or_default().insert(v);
92            self.level_adjacency[0].entry(v).or_default().insert(u);
93        }
94    }
95
96    /// Remove a non-tree edge
97    pub fn remove_non_tree_edge(&mut self, u: VertexId, v: VertexId) {
98        let key = normalize_edge(u, v);
99
100        if let Some(level) = self.edge_level.remove(&key) {
101            self.level_edges[level].remove(&key);
102
103            // Update adjacency
104            if let Some(adj) = self.level_adjacency[level].get_mut(&u) {
105                adj.remove(&v);
106            }
107            if let Some(adj) = self.level_adjacency[level].get_mut(&v) {
108                adj.remove(&u);
109            }
110        }
111    }
112
113    /// Find a replacement edge for deleted tree edge (u, v)
114    ///
115    /// Returns Some((x, y)) if a replacement exists, None if components disconnect.
116    ///
117    /// # Complexity
118    /// O(log n) amortized through level-based search
119    pub fn find_replacement(
120        &mut self,
121        u: VertexId,
122        v: VertexId,
123        tree_adjacency: &HashMap<VertexId, HashSet<VertexId>>,
124    ) -> Option<EdgeKey> {
125        let key = normalize_edge(u, v);
126
127        // The edge should be a tree edge
128        if !self.tree_edges.contains(&key) {
129            return None;
130        }
131
132        // Find smaller component using BFS on tree edges only
133        let (smaller_vertices, _larger_vertices) =
134            self.find_components_after_cut(u, v, tree_adjacency);
135
136        // Search for replacement edge across levels (from highest to 0)
137        for level in (0..self.max_level).rev() {
138            if let Some(replacement) = self.find_replacement_at_level(level, &smaller_vertices) {
139                return Some(replacement);
140            }
141        }
142
143        // No replacement found - promote edges from smaller component to next level
144        self.promote_edges(&smaller_vertices);
145
146        None
147    }
148
149    /// Fast replacement lookup when components are already known
150    ///
151    /// # Complexity
152    /// O(log n) - binary search through levels
153    pub fn find_replacement_fast(&self, smaller_component: &HashSet<VertexId>) -> Option<EdgeKey> {
154        // Search levels from 0 (most edges) upward
155        for level in 0..self.max_level {
156            if let Some(replacement) = self.find_replacement_at_level(level, smaller_component) {
157                return Some(replacement);
158            }
159        }
160        None
161    }
162
163    /// Find replacement at a specific level
164    fn find_replacement_at_level(
165        &self,
166        level: usize,
167        component: &HashSet<VertexId>,
168    ) -> Option<EdgeKey> {
169        // Look through adjacency at this level for edges crossing component boundary
170        for &vertex in component {
171            if let Some(neighbors) = self.level_adjacency[level].get(&vertex) {
172                for &neighbor in neighbors {
173                    if !component.contains(&neighbor) {
174                        // Found a crossing edge!
175                        return Some(normalize_edge(vertex, neighbor));
176                    }
177                }
178            }
179        }
180        None
181    }
182
183    /// Find the two components after cutting tree edge (u, v)
184    fn find_components_after_cut(
185        &self,
186        u: VertexId,
187        v: VertexId,
188        tree_adj: &HashMap<VertexId, HashSet<VertexId>>,
189    ) -> (HashSet<VertexId>, HashSet<VertexId>) {
190        let mut comp_u = HashSet::new();
191        let mut stack = vec![u];
192        comp_u.insert(u);
193
194        while let Some(x) = stack.pop() {
195            if let Some(neighbors) = tree_adj.get(&x) {
196                for &y in neighbors {
197                    // Skip the cut edge
198                    if (x == u && y == v) || (x == v && y == u) {
199                        continue;
200                    }
201                    if comp_u.insert(y) {
202                        stack.push(y);
203                    }
204                }
205            }
206        }
207
208        let mut comp_v = HashSet::new();
209        stack.push(v);
210        comp_v.insert(v);
211
212        while let Some(x) = stack.pop() {
213            if let Some(neighbors) = tree_adj.get(&x) {
214                for &y in neighbors {
215                    if (x == u && y == v) || (x == v && y == u) {
216                        continue;
217                    }
218                    if comp_v.insert(y) {
219                        stack.push(y);
220                    }
221                }
222            }
223        }
224
225        // Return smaller component first
226        if comp_u.len() <= comp_v.len() {
227            (comp_u, comp_v)
228        } else {
229            (comp_v, comp_u)
230        }
231    }
232
233    /// Promote non-tree edges from smaller component to next level
234    fn promote_edges(&mut self, component: &HashSet<VertexId>) {
235        let mut to_promote = Vec::new();
236
237        // Find edges at each level that have both endpoints in the component
238        for level in 0..self.max_level.saturating_sub(1) {
239            for &vertex in component {
240                if let Some(neighbors) = self.level_adjacency[level].get(&vertex).cloned() {
241                    for neighbor in neighbors {
242                        if component.contains(&neighbor) {
243                            let key = normalize_edge(vertex, neighbor);
244                            to_promote.push((key, level));
245                        }
246                    }
247                }
248            }
249        }
250
251        // Perform promotions
252        for (key, old_level) in to_promote {
253            let new_level = (old_level + 1).min(self.max_level - 1);
254            if new_level != old_level {
255                let (u, v) = key;
256
257                // Remove from old level
258                self.level_edges[old_level].remove(&key);
259                if let Some(adj) = self.level_adjacency[old_level].get_mut(&u) {
260                    adj.remove(&v);
261                }
262                if let Some(adj) = self.level_adjacency[old_level].get_mut(&v) {
263                    adj.remove(&u);
264                }
265
266                // Add to new level
267                self.level_edges[new_level].insert(key);
268                self.edge_level.insert(key, new_level);
269                self.level_adjacency[new_level]
270                    .entry(u)
271                    .or_default()
272                    .insert(v);
273                self.level_adjacency[new_level]
274                    .entry(v)
275                    .or_default()
276                    .insert(u);
277            }
278        }
279    }
280
281    /// Get statistics about the index
282    pub fn stats(&self) -> ReplacementIndexStats {
283        let edges_per_level: Vec<usize> = self.level_edges.iter().map(|s| s.len()).collect();
284
285        ReplacementIndexStats {
286            max_level: self.max_level,
287            tree_edges: self.tree_edges.len(),
288            non_tree_edges: self.edge_level.len(),
289            edges_per_level,
290        }
291    }
292}
293
294/// Statistics about the replacement edge index
295#[derive(Debug, Clone)]
296pub struct ReplacementIndexStats {
297    /// Maximum level (log₂ n)
298    pub max_level: usize,
299    /// Number of tree edges tracked
300    pub tree_edges: usize,
301    /// Number of non-tree edges across all levels
302    pub non_tree_edges: usize,
303    /// Count of edges at each level
304    pub edges_per_level: Vec<usize>,
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    #[test]
312    fn test_new_index() {
313        let idx = ReplacementEdgeIndex::new(100);
314        assert!(idx.max_level >= 7); // log2(100) ≈ 6.6
315        assert_eq!(idx.tree_edges.len(), 0);
316    }
317
318    #[test]
319    fn test_add_tree_edge() {
320        let mut idx = ReplacementEdgeIndex::new(10);
321        idx.add_tree_edge(1, 2);
322        idx.add_tree_edge(2, 3);
323
324        assert!(idx.tree_edges.contains(&(1, 2)));
325        assert!(idx.tree_edges.contains(&(2, 3)));
326    }
327
328    #[test]
329    fn test_add_non_tree_edge() {
330        let mut idx = ReplacementEdgeIndex::new(10);
331        idx.add_tree_edge(1, 2);
332        idx.add_non_tree_edge(1, 3);
333        idx.add_non_tree_edge(2, 4);
334
335        // Non-tree edge should be at level 0
336        assert!(idx.level_edges[0].contains(&(1, 3)));
337        assert!(idx.level_edges[0].contains(&(2, 4)));
338        assert_eq!(idx.edge_level.get(&(1, 3)), Some(&0));
339    }
340
341    #[test]
342    fn test_find_replacement_simple() {
343        let mut idx = ReplacementEdgeIndex::new(10);
344
345        // Tree: 1 - 2 - 3
346        idx.add_tree_edge(1, 2);
347        idx.add_tree_edge(2, 3);
348
349        // Non-tree edge: 1 - 3 (bypasses 2)
350        idx.add_non_tree_edge(1, 3);
351
352        // Build tree adjacency
353        let mut tree_adj: HashMap<VertexId, HashSet<VertexId>> = HashMap::new();
354        tree_adj.entry(1).or_default().insert(2);
355        tree_adj.entry(2).or_default().insert(1);
356        tree_adj.entry(2).or_default().insert(3);
357        tree_adj.entry(3).or_default().insert(2);
358
359        // Delete tree edge (1, 2) - should find (1, 3) as replacement
360        let replacement = idx.find_replacement(1, 2, &tree_adj);
361        assert_eq!(replacement, Some((1, 3)));
362    }
363
364    #[test]
365    fn test_find_replacement_none() {
366        let mut idx = ReplacementEdgeIndex::new(10);
367
368        // Tree: 1 - 2 - 3 (no non-tree edges)
369        idx.add_tree_edge(1, 2);
370        idx.add_tree_edge(2, 3);
371
372        let mut tree_adj: HashMap<VertexId, HashSet<VertexId>> = HashMap::new();
373        tree_adj.entry(1).or_default().insert(2);
374        tree_adj.entry(2).or_default().insert(1);
375        tree_adj.entry(2).or_default().insert(3);
376        tree_adj.entry(3).or_default().insert(2);
377
378        // No replacement for (1, 2)
379        let replacement = idx.find_replacement(1, 2, &tree_adj);
380        assert!(replacement.is_none());
381    }
382
383    #[test]
384    fn test_find_replacement_fast() {
385        let mut idx = ReplacementEdgeIndex::new(10);
386
387        // Non-tree edges at level 0
388        idx.add_non_tree_edge(1, 4);
389        idx.add_non_tree_edge(2, 5);
390
391        // Component {1, 2, 3}
392        let component: HashSet<VertexId> = [1, 2, 3].into_iter().collect();
393
394        // Should find (1, 4) or (2, 5) as crossing edge
395        let replacement = idx.find_replacement_fast(&component);
396        assert!(replacement.is_some());
397        let (u, v) = replacement.unwrap();
398        assert!(component.contains(&u) != component.contains(&v));
399    }
400
401    #[test]
402    fn test_stats() {
403        let mut idx = ReplacementEdgeIndex::new(100);
404        idx.add_tree_edge(1, 2);
405        idx.add_tree_edge(2, 3);
406        idx.add_non_tree_edge(1, 3);
407        idx.add_non_tree_edge(3, 4);
408
409        let stats = idx.stats();
410        assert_eq!(stats.tree_edges, 2);
411        assert_eq!(stats.non_tree_edges, 2);
412        assert_eq!(stats.edges_per_level[0], 2);
413    }
414
415    #[test]
416    fn test_remove_edges() {
417        let mut idx = ReplacementEdgeIndex::new(10);
418
419        idx.add_tree_edge(1, 2);
420        idx.add_non_tree_edge(1, 3);
421
422        assert!(idx.remove_tree_edge(1, 2));
423        assert!(!idx.tree_edges.contains(&(1, 2)));
424
425        idx.remove_non_tree_edge(1, 3);
426        assert!(!idx.level_edges[0].contains(&(1, 3)));
427    }
428}