Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Findmax(v,w) #24

Merged
merged 2 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}