-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
added a decremental connectivity data structure #684
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
Merged
vil02
merged 16 commits into
TheAlgorithms:master
from
Miraksi:decremental_connectivity
Apr 23, 2024
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
a53b44b
added a decremental connectivity data structure
Miraksi f07c46e
added another testcase for more code-coverage
Miraksi e7402f3
removed unnecessary function
Miraksi 4b490c2
added mem::swap in deletion
Miraksi 3292651
Revert "added mem::swap in deletion"
Miraksi 8b324f3
added more queries to the tests
Miraksi e0b8b3f
changed return type of is_smaller
Miraksi 2e989cc
removed unnecessary println
Miraksi 6bc69e5
improved assertions
Miraksi d9fe07d
added assertions to testing
03385c7
added checking for cycles
Miraksi f174437
added test for cyclic graphs
Miraksi fccd384
changed adjacency vector to use HashSets; fixed bug in deletion
Miraksi 71f5d3b
removed println
Miraksi 1300662
added check for non-directedness as well as a test case
Miraksi ad7e1a6
added test for faulty deletion input
Miraksi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
use std::collections::HashSet; | ||
|
||
/// A data-structure that, given a forest, allows dynamic-connectivity queries. | ||
/// Meaning deletion of an edge (u,v) and checking whether two vertecies are still connected. | ||
/// | ||
/// # Complexity | ||
/// The preprocessing phase runs in O(n) time, where n is the the number of vertecies in the forest. | ||
/// Deletion runs in O(log n) and checking for connectivity runs in O(1) time. | ||
/// | ||
/// # Sources | ||
/// used Uncyclopedia as reference: <https://en.wikipedia.org/wiki/Dynamic_connectivity> | ||
pub struct DecrementalConnectivity { | ||
adjacent: Vec<HashSet<usize>>, | ||
component: Vec<usize>, | ||
count: usize, | ||
visited: Vec<usize>, | ||
dfs_id: usize, | ||
} | ||
impl DecrementalConnectivity { | ||
//expects the parent of a root to be itself | ||
pub fn new(adjacent: Vec<HashSet<usize>>) -> Result<Self, String> { | ||
let n = adjacent.len(); | ||
if !is_forest(&adjacent) { | ||
return Err("input graph is not a forest!".to_string()); | ||
} | ||
let mut tmp = DecrementalConnectivity { | ||
adjacent, | ||
component: vec![0; n], | ||
count: 0, | ||
visited: vec![0; n], | ||
dfs_id: 1, | ||
}; | ||
tmp.component = tmp.calc_component(); | ||
Ok(tmp) | ||
} | ||
|
||
pub fn connected(&self, u: usize, v: usize) -> Option<bool> { | ||
match (self.component.get(u), self.component.get(v)) { | ||
(Some(a), Some(b)) => Some(a == b), | ||
_ => None, | ||
} | ||
} | ||
|
||
pub fn delete(&mut self, u: usize, v: usize) { | ||
if !self.adjacent[u].contains(&v) || self.component[u] != self.component[v] { | ||
panic!( | ||
"delete called on the edge ({}, {}) which doesn't exist", | ||
u, v | ||
); | ||
Miraksi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
self.adjacent[u].remove(&v); | ||
self.adjacent[v].remove(&u); | ||
|
||
let mut queue: Vec<usize> = Vec::new(); | ||
if self.is_smaller(u, v) { | ||
queue.push(u); | ||
self.dfs_id += 1; | ||
self.visited[v] = self.dfs_id; | ||
} else { | ||
queue.push(v); | ||
self.dfs_id += 1; | ||
self.visited[u] = self.dfs_id; | ||
} | ||
while !queue.is_empty() { | ||
let ¤t = queue.last().unwrap(); | ||
self.dfs_step(&mut queue, self.dfs_id); | ||
self.component[current] = self.count; | ||
} | ||
self.count += 1; | ||
} | ||
|
||
fn calc_component(&mut self) -> Vec<usize> { | ||
let mut visited: Vec<bool> = vec![false; self.adjacent.len()]; | ||
let mut comp: Vec<usize> = vec![0; self.adjacent.len()]; | ||
|
||
for i in 0..self.adjacent.len() { | ||
if visited[i] { | ||
continue; | ||
} | ||
let mut queue: Vec<usize> = vec![i]; | ||
while let Some(current) = queue.pop() { | ||
if !visited[current] { | ||
for &neighbour in self.adjacent[current].iter() { | ||
queue.push(neighbour); | ||
} | ||
} | ||
visited[current] = true; | ||
comp[current] = self.count; | ||
} | ||
self.count += 1; | ||
} | ||
comp | ||
} | ||
|
||
fn is_smaller(&mut self, u: usize, v: usize) -> bool { | ||
let mut u_queue: Vec<usize> = vec![u]; | ||
let u_id = self.dfs_id; | ||
self.visited[v] = u_id; | ||
self.dfs_id += 1; | ||
|
||
let mut v_queue: Vec<usize> = vec![v]; | ||
let v_id = self.dfs_id; | ||
self.visited[u] = v_id; | ||
self.dfs_id += 1; | ||
|
||
// parallel depth first search | ||
while !u_queue.is_empty() && !v_queue.is_empty() { | ||
self.dfs_step(&mut u_queue, u_id); | ||
self.dfs_step(&mut v_queue, v_id); | ||
} | ||
u_queue.is_empty() | ||
} | ||
|
||
fn dfs_step(&mut self, queue: &mut Vec<usize>, dfs_id: usize) { | ||
let u = queue.pop().unwrap(); | ||
self.visited[u] = dfs_id; | ||
for &v in self.adjacent[u].iter() { | ||
if self.visited[v] == dfs_id { | ||
continue; | ||
} | ||
queue.push(v); | ||
} | ||
} | ||
} | ||
|
||
// checks whether the given graph is a forest | ||
// also checks for all adjacent vertices a,b if adjacent[a].contains(b) && adjacent[b].contains(a) | ||
fn is_forest(adjacent: &Vec<HashSet<usize>>) -> bool { | ||
let mut visited = vec![false; adjacent.len()]; | ||
for node in 0..adjacent.len() { | ||
if visited[node] { | ||
continue; | ||
} | ||
if has_cycle(adjacent, &mut visited, node, node) { | ||
return false; | ||
} | ||
} | ||
true | ||
} | ||
|
||
fn has_cycle( | ||
adjacent: &Vec<HashSet<usize>>, | ||
visited: &mut Vec<bool>, | ||
node: usize, | ||
parent: usize, | ||
) -> bool { | ||
visited[node] = true; | ||
for &neighbour in adjacent[node].iter() { | ||
if !adjacent[neighbour].contains(&node) { | ||
panic!("the given graph does not strictly contain bidirectional edges\n {} -> {} exists, but the other direction does not", node, neighbour); | ||
} | ||
if !visited[neighbour] { | ||
if has_cycle(adjacent, visited, neighbour, node) { | ||
return true; | ||
} | ||
} else if neighbour != parent { | ||
return true; | ||
} | ||
} | ||
false | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use std::collections::HashSet; | ||
|
||
// test forest (remember the assumptoin that roots are adjacent to themselves) | ||
// _ _ | ||
// \ / \ / | ||
// 0 7 | ||
// / | \ | | ||
// 1 2 3 8 | ||
// / / \ | ||
// 4 5 6 | ||
#[test] | ||
fn construction_test() { | ||
let mut adjacent = vec![ | ||
HashSet::from([0, 1, 2, 3]), | ||
HashSet::from([0, 4]), | ||
HashSet::from([0, 5, 6]), | ||
HashSet::from([0]), | ||
HashSet::from([1]), | ||
HashSet::from([2]), | ||
HashSet::from([2]), | ||
HashSet::from([7, 8]), | ||
HashSet::from([7]), | ||
]; | ||
let dec_con = super::DecrementalConnectivity::new(adjacent.clone()).unwrap(); | ||
assert_eq!(dec_con.component, vec![0, 0, 0, 0, 0, 0, 0, 1, 1]); | ||
|
||
// add a cycle to the tree | ||
adjacent[2].insert(4); | ||
adjacent[4].insert(2); | ||
assert!(super::DecrementalConnectivity::new(adjacent.clone()).is_err()); | ||
} | ||
#[test] | ||
#[should_panic(expected = "2 -> 4 exists")] | ||
fn non_bidirectional_test() { | ||
let adjacent = vec![ | ||
HashSet::from([0, 1, 2, 3]), | ||
HashSet::from([0, 4]), | ||
HashSet::from([0, 5, 6, 4]), | ||
HashSet::from([0]), | ||
HashSet::from([1]), | ||
HashSet::from([2]), | ||
HashSet::from([2]), | ||
HashSet::from([7, 8]), | ||
HashSet::from([7]), | ||
]; | ||
|
||
// should panic now since our graph is not bidirectional | ||
super::DecrementalConnectivity::new(adjacent).unwrap(); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected = "delete called on the edge (2, 4)")] | ||
fn delete_panic_test() { | ||
let adjacent = vec![ | ||
HashSet::from([0, 1, 2, 3]), | ||
HashSet::from([0, 4]), | ||
HashSet::from([0, 5, 6]), | ||
HashSet::from([0]), | ||
HashSet::from([1]), | ||
HashSet::from([2]), | ||
HashSet::from([2]), | ||
HashSet::from([7, 8]), | ||
HashSet::from([7]), | ||
]; | ||
let mut dec_con = super::DecrementalConnectivity::new(adjacent.clone()).unwrap(); | ||
dec_con.delete(2, 4); | ||
} | ||
|
||
#[test] | ||
fn query_test() { | ||
let adjacent = vec![ | ||
HashSet::from([0, 1, 2, 3]), | ||
HashSet::from([0, 4]), | ||
HashSet::from([0, 5, 6]), | ||
HashSet::from([0]), | ||
HashSet::from([1]), | ||
HashSet::from([2]), | ||
HashSet::from([2]), | ||
HashSet::from([7, 8]), | ||
HashSet::from([7]), | ||
]; | ||
let mut dec_con1 = super::DecrementalConnectivity::new(adjacent.clone()).unwrap(); | ||
assert!(dec_con1.connected(3, 4).unwrap()); | ||
assert!(dec_con1.connected(5, 0).unwrap()); | ||
assert!(!dec_con1.connected(2, 7).unwrap()); | ||
assert!(dec_con1.connected(0, 9).is_none()); | ||
dec_con1.delete(0, 2); | ||
assert!(dec_con1.connected(3, 4).unwrap()); | ||
assert!(!dec_con1.connected(5, 0).unwrap()); | ||
assert!(dec_con1.connected(5, 6).unwrap()); | ||
assert!(dec_con1.connected(8, 7).unwrap()); | ||
dec_con1.delete(7, 8); | ||
assert!(!dec_con1.connected(8, 7).unwrap()); | ||
dec_con1.delete(1, 4); | ||
assert!(!dec_con1.connected(1, 4).unwrap()); | ||
|
||
let mut dec_con2 = super::DecrementalConnectivity::new(adjacent.clone()).unwrap(); | ||
dec_con2.delete(4, 1); | ||
assert!(!dec_con2.connected(1, 4).unwrap()); | ||
|
||
let mut dec_con3 = super::DecrementalConnectivity::new(adjacent.clone()).unwrap(); | ||
dec_con3.delete(1, 4); | ||
assert!(!dec_con3.connected(4, 1).unwrap()); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.