Skip to content

Commit

Permalink
closes #14
Browse files Browse the repository at this point in the history
  • Loading branch information
azizkayumov committed Oct 12, 2023
1 parent 78e66cf commit 98217e9
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 5 deletions.
48 changes: 47 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}
72 changes: 68 additions & 4 deletions tests/test_connectivity.rs → tests/test_random.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -44,6 +48,46 @@ fn connected_components(edges: &HashSet<(usize, usize)>) -> Vec<usize> {
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::<Vec<usize>>();
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;

Expand All @@ -52,27 +96,34 @@ 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();

// 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 component_ids = (0..NUMBER_OF_NODES).collect::<Vec<usize>>();
let mut weights = (0..NUMBER_OF_NODES).map(|i| i as f64).collect::<Vec<_>>();
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:
Expand All @@ -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
}

0 comments on commit 98217e9

Please sign in to comment.