Skip to content

Commit

Permalink
Merge pull request #24 from azizkayumov/develop
Browse files Browse the repository at this point in the history
Findmax(v,w)
  • Loading branch information
azizkayumov authored Oct 12, 2023
2 parents ddede28 + 98217e9 commit 91b5a24
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 23 deletions.
82 changes: 63 additions & 19 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl LinkCutTree {
}

/// Constructs a path from a node to the root of the tree.
pub fn access(&mut self, v: usize) {
fn access(&mut self, v: usize) {
splay(&mut self.forest, v);

if let Some(right_idx) = self.forest[v].right {
Expand Down Expand Up @@ -54,23 +54,12 @@ impl LinkCutTree {
}

/// Makes v the root of its represented tree by flipping the path from v to the root.
pub fn reroot(&mut self, v: usize) {
fn reroot(&mut self, v: usize) {
self.access(v);
self.forest[v].flipped ^= true;
unflip(&mut self.forest, v);
}

/// 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) {
self.reroot(v);
self.access(w);
if !matches!(self.forest[v].parent, Parent::Root) || v == w {
return; // already connected
}
self.forest[v].left = Some(w); // v is the root of its represented tree, so no need to check if it has a left child
self.forest[w].parent = Parent::Node(v);
}

/// Checks if v and w are connected in the forest.
pub fn connected(&mut self, v: usize, w: usize) -> bool {
self.reroot(v); // v is now the root of the tree
Expand All @@ -79,12 +68,20 @@ impl LinkCutTree {
!matches!(self.forest[v].parent, Parent::Root) || v == w
}

/// 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;
}
// 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);
self.forest[w].parent = Parent::Node(v);
}

/// Cuts the link between nodes v and w (if it exists)
pub fn cut(&mut self, v: usize, w: usize) {
self.reroot(v);
self.access(w);
if matches!(self.forest[v].parent, Parent::Root) || v == w {
return; // not connected
if !self.connected(v, w) {
return;
}
// detach w from its parent (which is v)
if let Some(left) = self.forest[w].left {
Expand All @@ -95,8 +92,9 @@ impl LinkCutTree {

/// Finds the maximum weight in the path from nodes v and w (if they are connected)
pub fn findmax(&mut self, v: usize, w: usize) -> usize {
self.reroot(v);
self.access(w);
if !self.connected(v, w) {
return usize::MAX;
}
self.forest[w].max_weight_idx
}

Expand Down Expand Up @@ -357,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 91b5a24

Please sign in to comment.