diff --git a/src/lib.rs b/src/lib.rs index 904bed1..7371ce7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,7 @@ impl LinkCutTree { /// Creates a link between two nodes in the forest (where w is the parent of v). pub fn link(&mut self, v: usize, w: usize) { if self.connected(v, w) { - return; // already connected + return; } // v is the root of its represented tree, so no need to check if it has a left child self.forest[v].left = Some(w); @@ -355,4 +355,50 @@ mod tests { assert_eq!(lctree.findroot(i), 1); } } + + #[test] + pub fn findmax() { + let mut lctree = super::LinkCutTree::new(10); + // [9.0, 1.0, 8.0, 0.0, 6.0, 2.0, 4.0, 3.0, 7.0, 5.0] + lctree.forest[0].weight = 9.0; + lctree.forest[1].weight = 1.0; + lctree.forest[2].weight = 8.0; + lctree.forest[3].weight = 0.0; + lctree.forest[4].weight = 6.0; + lctree.forest[5].weight = 2.0; + lctree.forest[6].weight = 4.0; + lctree.forest[7].weight = 3.0; + lctree.forest[8].weight = 7.0; + lctree.forest[9].weight = 5.0; + + // We form a link-cut tree from a rooted tree with the following structure + // (the numbers in parentheses are the weights of the nodes): + // 0(9) + // / \ + // 1(1) 5(2) + // / \ \ + // 2(8) 3(0) 6(4) + // / \ + // 4(6) 7(3) + // / \ + // 8(7) 9(5) + lctree.link(1, 0); + lctree.link(2, 1); + lctree.link(3, 1); + lctree.link(4, 2); + lctree.link(5, 0); + lctree.link(6, 5); + lctree.link(7, 6); + lctree.link(8, 7); + lctree.link(9, 7); + + // We check the node index with max weight in the path from each node to the root: + assert_eq!(lctree.findmax(4, 5), 0); + assert_eq!(lctree.findmax(3, 6), 0); + assert_eq!(lctree.findmax(2, 7), 0); + assert_eq!(lctree.findmax(1, 8), 0); + assert_eq!(lctree.findmax(0, 9), 0); + assert_eq!(lctree.findmax(4, 3), 2); + assert_eq!(lctree.findmax(5, 7), 6); + } } diff --git a/tests/test_connectivity.rs b/tests/test_random.rs similarity index 58% rename from tests/test_connectivity.rs rename to tests/test_random.rs index 48ed161..6d5f2f4 100644 --- a/tests/test_connectivity.rs +++ b/tests/test_random.rs @@ -1,5 +1,9 @@ use lctree::LinkCutTree; -use rand::{rngs::StdRng, seq::IteratorRandom, Rng, SeedableRng}; +use rand::{ + rngs::StdRng, + seq::{IteratorRandom, SliceRandom}, + Rng, SeedableRng, +}; use rand_derive2::RandGen; use std::collections::HashSet; @@ -44,6 +48,46 @@ fn connected_components(edges: &HashSet<(usize, usize)>) -> Vec { component_ids } +fn findmax_brute_force( + v: usize, + w: usize, + edges: &HashSet<(usize, usize)>, + weights: &[f64], +) -> usize { + // create adjacency list from edges + let mut adj = vec![vec![]; NUMBER_OF_NODES]; + for (v, w) in edges { + adj[*v].push(*w); + adj[*w].push(*v); + } + + let mut findmax = (0..NUMBER_OF_NODES).collect::>(); + let mut visited = vec![false; NUMBER_OF_NODES]; + let mut stack = vec![(v, v)]; + + // dfs from v to w, keeping track of the maximum weight in the path + while let Some((prev, cur)) = stack.pop() { + if visited[cur] { + continue; + } + visited[cur] = true; + if weights[findmax[prev]] > weights[findmax[cur]] { + findmax[cur] = findmax[prev] + } + + if cur == w { + break; + } + + for &next in &adj[cur] { + if !visited[next] { + stack.push((cur, next)); + } + } + } + findmax[w] +} + const NUMBER_OF_NODES: usize = 100; const NUMBER_OF_OPERATIONS: usize = 2000; @@ -52,11 +96,11 @@ enum Operation { Link, Cut, Connected, + Findmax, } #[test] pub fn connectivity() { - let now = std::time::Instant::now(); let mut rng = create_random_generator(); let mut edges = HashSet::new(); @@ -64,15 +108,22 @@ pub fn connectivity() { // (edges are not added yet): let mut lctree = LinkCutTree::new(NUMBER_OF_NODES); let mut component_ids = (0..NUMBER_OF_NODES).collect::>(); + let mut weights = (0..NUMBER_OF_NODES).map(|i| i as f64).collect::>(); + weights.shuffle(&mut rng); + for i in 0..NUMBER_OF_NODES { + lctree.set_weight(i, weights[i]); + } // perform random operations: link, cut, or connected: for _ in 0..NUMBER_OF_OPERATIONS { let operation: Operation = rng.gen(); match operation { Operation::Link => { + // Choose two random nodes to link: let v = rng.gen_range(0..NUMBER_OF_NODES); let w = rng.gen_range(0..NUMBER_OF_NODES); - lctree.link(v, w); // ignores if v and w are already connected + + lctree.link(v, w); // ignores cycles // We only add the edge if it connects two different trees, // we don't want to create cycles: @@ -85,18 +136,31 @@ pub fn connectivity() { if edges.is_empty() { continue; } + // Choose a random edge to cut: let (v, w) = edges.iter().choose(&mut rng).unwrap(); lctree.cut(*v, *w); + // Remove the edge and update the component ids: edges.remove(&(*v, *w)); component_ids = connected_components(&edges); } Operation::Connected => { + // Choose two random nodes to check if they are connected: let v = rng.gen_range(0..NUMBER_OF_NODES); let w = rng.gen_range(0..NUMBER_OF_NODES); assert_eq!(lctree.connected(v, w), component_ids[v] == component_ids[w]); } + Operation::Findmax => { + // Choose two random nodes from the same tree to find the node + // with the maximum weight in the path between them: + let v = rng.gen_range(0..NUMBER_OF_NODES); + let w = (0..NUMBER_OF_NODES) + .filter(|&w| component_ids[w] == component_ids[v]) + .choose(&mut rng) + .unwrap(); + let expected = findmax_brute_force(v, w, &edges, &weights); + assert_eq!(lctree.findmax(v, w), expected); + } } } - println!("Time: {}ms", now.elapsed().as_millis()); // Time: 40ms }