diff --git a/Cargo.toml b/Cargo.toml index 0c4332c..f1e6431 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ edition = "2021" [dev-dependencies] rand = "0.8" +rand_derive2 = "0.1.21" diff --git a/src/lib.rs b/src/lib.rs index 8e2a384..1c3f4fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,7 +51,6 @@ impl LinkCutTree { } /// Creates a link between two nodes in the forest (v becomes the parent of w) - /// # Panics if v has a left child (i.e. v is not a root node) pub fn link(&mut self, v: usize, w: usize) { self.access(v); self.access(w); diff --git a/tests/test_connectivity.rs b/tests/test_connectivity.rs new file mode 100644 index 0000000..0467283 --- /dev/null +++ b/tests/test_connectivity.rs @@ -0,0 +1,112 @@ +use lctree::LinkCutTree; +use rand::{rngs::StdRng, seq::IteratorRandom, Rng, SeedableRng}; +use rand_derive2::RandGen; +use std::collections::HashSet; + +fn create_random_generator() -> StdRng { + let seed = rand::thread_rng().gen(); + println!("Seed: {}", seed); // print seed so we can reproduce the test (if it fails). + StdRng::seed_from_u64(seed) +} + +fn create_random_tree(rng: &mut StdRng) -> Vec<(usize, usize)> { + let mut nodes = Vec::from([0]); + let mut edges = Vec::new(); + for i in 1..NUMBER_OF_NODES { + let parent = nodes[rng.gen_range(0..i)]; + nodes.push(i); + edges.push((i, parent)); + } + edges +} + +fn dfs( + v: usize, + adj: &Vec>, + visited: &mut Vec, + component_ids: &mut Vec, + component_id: usize, +) { + visited[v] = true; + component_ids[v] = component_id; + for &w in &adj[v] { + if !visited[w] { + dfs(w, adj, visited, component_ids, component_id); + } + } +} + +fn connected_components(edges: &HashSet<(usize, usize)>) -> Vec { + // 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); + } + + // explore each component using dfs and assign component ids + let mut visited = vec![false; NUMBER_OF_NODES]; + let mut component_ids = vec![0; NUMBER_OF_NODES]; + let mut component_id = 0; + for v in 0..NUMBER_OF_NODES { + if !visited[v] { + component_id += 1; + dfs(v, &adj, &mut visited, &mut component_ids, component_id); + } + } + component_ids +} + +const NUMBER_OF_NODES: usize = 100; +const NUMBER_OF_OPERATIONS: usize = 1000; + +#[derive(RandGen)] +enum Operation { + Link, + Cut, + Connected, +} + +#[test] +pub fn test_connectivity() { + let mut rng = create_random_generator(); + let edges = create_random_tree(&mut rng); + + // initialize link-cut tree, we start with a forest of single nodes + // (edges are not added yet): + let mut lctree = LinkCutTree::new(NUMBER_OF_NODES); + let mut edges_in_tree = HashSet::new(); + let mut component_ids = (0..NUMBER_OF_NODES).collect::>(); + + // perform random operations: link, cut, or connected: + for _ in 0..NUMBER_OF_OPERATIONS { + let operation: Operation = rng.gen(); + match operation { + Operation::Link => { + let (v, w) = edges.iter().choose(&mut rng).unwrap(); + println!("Link {} {}", v, w); + lctree.link(*v, *w); + + edges_in_tree.insert((*v, *w)); + component_ids = connected_components(&edges_in_tree); + } + Operation::Cut => { + if edges_in_tree.is_empty() { + continue; + } + let (v, w) = edges_in_tree.iter().choose(&mut rng).unwrap(); + println!("Cut {} {}", v, w); + lctree.cut(*v); + + edges_in_tree.remove(&(*v, *w)); + component_ids = connected_components(&edges_in_tree); + } + Operation::Connected => { + let v = rng.gen_range(0..NUMBER_OF_NODES); + let w = rng.gen_range(0..NUMBER_OF_NODES); + println!("Connected {} {}", v, w); + assert_eq!(lctree.connected(v, w), component_ids[v] == component_ids[w]); + } + } + } +} diff --git a/tests/test_findmax.rs b/tests/test_findmax.rs new file mode 100644 index 0000000..d3f7f2e --- /dev/null +++ b/tests/test_findmax.rs @@ -0,0 +1,55 @@ +use lctree::LinkCutTree; +use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng}; + +fn create_random_generator() -> StdRng { + let seed = rand::thread_rng().gen(); + println!("Seed: {}", seed); // print seed so we can reproduce the test (if it fails). + StdRng::seed_from_u64(seed) +} + +fn create_random_tree(rng: &mut StdRng) -> (Vec<(usize, usize)>, Vec, Vec) { + let mut edges = Vec::new(); + let mut weights: Vec = (0..NUMBER_OF_NODES).map(|i| i as f64).collect(); + weights.shuffle(rng); + + let mut max_to_root = vec![0; NUMBER_OF_NODES]; + let mut in_tree = Vec::from([0]); + for i in 1..NUMBER_OF_NODES { + let parent_idx = rng.gen_range(0..in_tree.len()); + let parent = in_tree[parent_idx]; + edges.push((i, parent)); + + max_to_root[i] = if weights[i] > weights[max_to_root[parent]] { + i + } else { + max_to_root[parent] + }; + in_tree.push(i); + } + + (edges, weights, max_to_root) +} + +const NUMBER_OF_NODES: usize = 1000; + +#[test] +pub fn findmax_random() { + let mut rng = create_random_generator(); + let (edges, weights, expected_findmax) = create_random_tree(&mut rng); + + // initialize link-cut tree + let mut lctree = LinkCutTree::new(NUMBER_OF_NODES); + for i in 0..NUMBER_OF_NODES { + lctree.set_weight(i, weights[i]); + } + for (v, w) in edges { + lctree.link(v, w); + } + + // perform random findmax queries + let mut nodes = (0..NUMBER_OF_NODES).collect::>(); + nodes.shuffle(&mut rng); + for v in nodes { + assert_eq!(lctree.findmax(v), expected_findmax[v]); + } +} diff --git a/tests/test_findmax_random.rs b/tests/test_findmax_random.rs deleted file mode 100644 index efeab71..0000000 --- a/tests/test_findmax_random.rs +++ /dev/null @@ -1,47 +0,0 @@ -use lctree::LinkCutTree; -use rand::{seq::SliceRandom, Rng, SeedableRng}; - -fn create_random_tree(n: usize, seed: u64) -> (Vec<(usize, usize)>, Vec, Vec) { - let mut rng = rand::rngs::StdRng::seed_from_u64(seed); - - let mut edges = Vec::new(); - let mut weights: Vec = (0..n).map(|i| i as f64).collect(); - weights.shuffle(&mut rng); - - let mut max_to_root = vec![0; n]; - let mut in_tree = Vec::from([0]); - for i in 1..n { - let parent_idx = rng.gen_range(0..in_tree.len()); - let parent = in_tree[parent_idx]; - edges.push((i, parent)); - - max_to_root[i] = if weights[i] > weights[max_to_root[parent]] { - i - } else { - max_to_root[parent] - }; - in_tree.push(i); - } - - (edges, weights, max_to_root) -} - -#[test] -pub fn findmax_random() { - let n = 100; - let seed = rand::thread_rng().gen(); - let (edges, weights, max_to_root) = create_random_tree(n, seed); - let mut lctree = LinkCutTree::new(n); - for i in 0..n { - lctree.set_weight(i, weights[i]); - } - - for (v, w) in edges { - lctree.link(v, w); - } - - for _ in 0..n * 100 { - let v = rand::thread_rng().gen_range(0..n); - assert_eq!(lctree.findmax(v), max_to_root[v]); - } -}