Skip to content

Commit b20faef

Browse files
Miraksivil02arne.prinzler
authored
added a decremental connectivity data structure (#684)
* added a decremental connectivity data structure * added another testcase for more code-coverage * removed unnecessary function * added mem::swap in deletion * Revert "added mem::swap in deletion" * added more queries to the tests * changed return type of is_smaller * removed unnecessary println Co-authored-by: Piotr Idzik <[email protected]> * improved assertions Co-authored-by: Piotr Idzik <[email protected]> * added assertions to testing * added checking for cycles * added test for cyclic graphs * changed adjacency vector to use HashSets; fixed bug in deletion * removed println Co-authored-by: Piotr Idzik <[email protected]> * added check for non-directedness as well as a test case * added test for faulty deletion input --------- Co-authored-by: Piotr Idzik <[email protected]> Co-authored-by: arne.prinzler <[email protected]>
1 parent 4e236ea commit b20faef

File tree

3 files changed

+273
-0
lines changed

3 files changed

+273
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
* [Bipartite Matching](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/bipartite_matching.rs)
121121
* [Breadth First Search](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/breadth_first_search.rs)
122122
* [Centroid Decomposition](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/centroid_decomposition.rs)
123+
* [Decremental Connectivity](https://github.com/TheAlgorithms/Rust/blob/master/src/decremental_connectivity/decremental_connectivity.rs)
123124
* [Depth First Search](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/depth_first_search.rs)
124125
* [Depth First Search Tic Tac Toe](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/depth_first_search_tic_tac_toe.rs)
125126
* [Dijkstra](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/dijkstra.rs)

src/graph/decremental_connectivity.rs

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
use std::collections::HashSet;
2+
3+
/// A data-structure that, given a forest, allows dynamic-connectivity queries.
4+
/// Meaning deletion of an edge (u,v) and checking whether two vertecies are still connected.
5+
///
6+
/// # Complexity
7+
/// The preprocessing phase runs in O(n) time, where n is the the number of vertecies in the forest.
8+
/// Deletion runs in O(log n) and checking for connectivity runs in O(1) time.
9+
///
10+
/// # Sources
11+
/// used Uncyclopedia as reference: <https://en.wikipedia.org/wiki/Dynamic_connectivity>
12+
pub struct DecrementalConnectivity {
13+
adjacent: Vec<HashSet<usize>>,
14+
component: Vec<usize>,
15+
count: usize,
16+
visited: Vec<usize>,
17+
dfs_id: usize,
18+
}
19+
impl DecrementalConnectivity {
20+
//expects the parent of a root to be itself
21+
pub fn new(adjacent: Vec<HashSet<usize>>) -> Result<Self, String> {
22+
let n = adjacent.len();
23+
if !is_forest(&adjacent) {
24+
return Err("input graph is not a forest!".to_string());
25+
}
26+
let mut tmp = DecrementalConnectivity {
27+
adjacent,
28+
component: vec![0; n],
29+
count: 0,
30+
visited: vec![0; n],
31+
dfs_id: 1,
32+
};
33+
tmp.component = tmp.calc_component();
34+
Ok(tmp)
35+
}
36+
37+
pub fn connected(&self, u: usize, v: usize) -> Option<bool> {
38+
match (self.component.get(u), self.component.get(v)) {
39+
(Some(a), Some(b)) => Some(a == b),
40+
_ => None,
41+
}
42+
}
43+
44+
pub fn delete(&mut self, u: usize, v: usize) {
45+
if !self.adjacent[u].contains(&v) || self.component[u] != self.component[v] {
46+
panic!(
47+
"delete called on the edge ({}, {}) which doesn't exist",
48+
u, v
49+
);
50+
}
51+
52+
self.adjacent[u].remove(&v);
53+
self.adjacent[v].remove(&u);
54+
55+
let mut queue: Vec<usize> = Vec::new();
56+
if self.is_smaller(u, v) {
57+
queue.push(u);
58+
self.dfs_id += 1;
59+
self.visited[v] = self.dfs_id;
60+
} else {
61+
queue.push(v);
62+
self.dfs_id += 1;
63+
self.visited[u] = self.dfs_id;
64+
}
65+
while !queue.is_empty() {
66+
let &current = queue.last().unwrap();
67+
self.dfs_step(&mut queue, self.dfs_id);
68+
self.component[current] = self.count;
69+
}
70+
self.count += 1;
71+
}
72+
73+
fn calc_component(&mut self) -> Vec<usize> {
74+
let mut visited: Vec<bool> = vec![false; self.adjacent.len()];
75+
let mut comp: Vec<usize> = vec![0; self.adjacent.len()];
76+
77+
for i in 0..self.adjacent.len() {
78+
if visited[i] {
79+
continue;
80+
}
81+
let mut queue: Vec<usize> = vec![i];
82+
while let Some(current) = queue.pop() {
83+
if !visited[current] {
84+
for &neighbour in self.adjacent[current].iter() {
85+
queue.push(neighbour);
86+
}
87+
}
88+
visited[current] = true;
89+
comp[current] = self.count;
90+
}
91+
self.count += 1;
92+
}
93+
comp
94+
}
95+
96+
fn is_smaller(&mut self, u: usize, v: usize) -> bool {
97+
let mut u_queue: Vec<usize> = vec![u];
98+
let u_id = self.dfs_id;
99+
self.visited[v] = u_id;
100+
self.dfs_id += 1;
101+
102+
let mut v_queue: Vec<usize> = vec![v];
103+
let v_id = self.dfs_id;
104+
self.visited[u] = v_id;
105+
self.dfs_id += 1;
106+
107+
// parallel depth first search
108+
while !u_queue.is_empty() && !v_queue.is_empty() {
109+
self.dfs_step(&mut u_queue, u_id);
110+
self.dfs_step(&mut v_queue, v_id);
111+
}
112+
u_queue.is_empty()
113+
}
114+
115+
fn dfs_step(&mut self, queue: &mut Vec<usize>, dfs_id: usize) {
116+
let u = queue.pop().unwrap();
117+
self.visited[u] = dfs_id;
118+
for &v in self.adjacent[u].iter() {
119+
if self.visited[v] == dfs_id {
120+
continue;
121+
}
122+
queue.push(v);
123+
}
124+
}
125+
}
126+
127+
// checks whether the given graph is a forest
128+
// also checks for all adjacent vertices a,b if adjacent[a].contains(b) && adjacent[b].contains(a)
129+
fn is_forest(adjacent: &Vec<HashSet<usize>>) -> bool {
130+
let mut visited = vec![false; adjacent.len()];
131+
for node in 0..adjacent.len() {
132+
if visited[node] {
133+
continue;
134+
}
135+
if has_cycle(adjacent, &mut visited, node, node) {
136+
return false;
137+
}
138+
}
139+
true
140+
}
141+
142+
fn has_cycle(
143+
adjacent: &Vec<HashSet<usize>>,
144+
visited: &mut Vec<bool>,
145+
node: usize,
146+
parent: usize,
147+
) -> bool {
148+
visited[node] = true;
149+
for &neighbour in adjacent[node].iter() {
150+
if !adjacent[neighbour].contains(&node) {
151+
panic!("the given graph does not strictly contain bidirectional edges\n {} -> {} exists, but the other direction does not", node, neighbour);
152+
}
153+
if !visited[neighbour] {
154+
if has_cycle(adjacent, visited, neighbour, node) {
155+
return true;
156+
}
157+
} else if neighbour != parent {
158+
return true;
159+
}
160+
}
161+
false
162+
}
163+
164+
#[cfg(test)]
165+
mod tests {
166+
use std::collections::HashSet;
167+
168+
// test forest (remember the assumptoin that roots are adjacent to themselves)
169+
// _ _
170+
// \ / \ /
171+
// 0 7
172+
// / | \ |
173+
// 1 2 3 8
174+
// / / \
175+
// 4 5 6
176+
#[test]
177+
fn construction_test() {
178+
let mut adjacent = vec![
179+
HashSet::from([0, 1, 2, 3]),
180+
HashSet::from([0, 4]),
181+
HashSet::from([0, 5, 6]),
182+
HashSet::from([0]),
183+
HashSet::from([1]),
184+
HashSet::from([2]),
185+
HashSet::from([2]),
186+
HashSet::from([7, 8]),
187+
HashSet::from([7]),
188+
];
189+
let dec_con = super::DecrementalConnectivity::new(adjacent.clone()).unwrap();
190+
assert_eq!(dec_con.component, vec![0, 0, 0, 0, 0, 0, 0, 1, 1]);
191+
192+
// add a cycle to the tree
193+
adjacent[2].insert(4);
194+
adjacent[4].insert(2);
195+
assert!(super::DecrementalConnectivity::new(adjacent.clone()).is_err());
196+
}
197+
#[test]
198+
#[should_panic(expected = "2 -> 4 exists")]
199+
fn non_bidirectional_test() {
200+
let adjacent = vec![
201+
HashSet::from([0, 1, 2, 3]),
202+
HashSet::from([0, 4]),
203+
HashSet::from([0, 5, 6, 4]),
204+
HashSet::from([0]),
205+
HashSet::from([1]),
206+
HashSet::from([2]),
207+
HashSet::from([2]),
208+
HashSet::from([7, 8]),
209+
HashSet::from([7]),
210+
];
211+
212+
// should panic now since our graph is not bidirectional
213+
super::DecrementalConnectivity::new(adjacent).unwrap();
214+
}
215+
216+
#[test]
217+
#[should_panic(expected = "delete called on the edge (2, 4)")]
218+
fn delete_panic_test() {
219+
let adjacent = vec![
220+
HashSet::from([0, 1, 2, 3]),
221+
HashSet::from([0, 4]),
222+
HashSet::from([0, 5, 6]),
223+
HashSet::from([0]),
224+
HashSet::from([1]),
225+
HashSet::from([2]),
226+
HashSet::from([2]),
227+
HashSet::from([7, 8]),
228+
HashSet::from([7]),
229+
];
230+
let mut dec_con = super::DecrementalConnectivity::new(adjacent.clone()).unwrap();
231+
dec_con.delete(2, 4);
232+
}
233+
234+
#[test]
235+
fn query_test() {
236+
let adjacent = vec![
237+
HashSet::from([0, 1, 2, 3]),
238+
HashSet::from([0, 4]),
239+
HashSet::from([0, 5, 6]),
240+
HashSet::from([0]),
241+
HashSet::from([1]),
242+
HashSet::from([2]),
243+
HashSet::from([2]),
244+
HashSet::from([7, 8]),
245+
HashSet::from([7]),
246+
];
247+
let mut dec_con1 = super::DecrementalConnectivity::new(adjacent.clone()).unwrap();
248+
assert!(dec_con1.connected(3, 4).unwrap());
249+
assert!(dec_con1.connected(5, 0).unwrap());
250+
assert!(!dec_con1.connected(2, 7).unwrap());
251+
assert!(dec_con1.connected(0, 9).is_none());
252+
dec_con1.delete(0, 2);
253+
assert!(dec_con1.connected(3, 4).unwrap());
254+
assert!(!dec_con1.connected(5, 0).unwrap());
255+
assert!(dec_con1.connected(5, 6).unwrap());
256+
assert!(dec_con1.connected(8, 7).unwrap());
257+
dec_con1.delete(7, 8);
258+
assert!(!dec_con1.connected(8, 7).unwrap());
259+
dec_con1.delete(1, 4);
260+
assert!(!dec_con1.connected(1, 4).unwrap());
261+
262+
let mut dec_con2 = super::DecrementalConnectivity::new(adjacent.clone()).unwrap();
263+
dec_con2.delete(4, 1);
264+
assert!(!dec_con2.connected(1, 4).unwrap());
265+
266+
let mut dec_con3 = super::DecrementalConnectivity::new(adjacent.clone()).unwrap();
267+
dec_con3.delete(1, 4);
268+
assert!(!dec_con3.connected(4, 1).unwrap());
269+
}
270+
}

src/graph/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod bellman_ford;
33
mod bipartite_matching;
44
mod breadth_first_search;
55
mod centroid_decomposition;
6+
mod decremental_connectivity;
67
mod depth_first_search;
78
mod depth_first_search_tic_tac_toe;
89
mod dijkstra;
@@ -29,6 +30,7 @@ pub use self::bellman_ford::bellman_ford;
2930
pub use self::bipartite_matching::BipartiteMatching;
3031
pub use self::breadth_first_search::breadth_first_search;
3132
pub use self::centroid_decomposition::CentroidDecomposition;
33+
pub use self::decremental_connectivity::DecrementalConnectivity;
3234
pub use self::depth_first_search::depth_first_search;
3335
pub use self::depth_first_search_tic_tac_toe::minimax;
3436
pub use self::dijkstra::dijkstra;

0 commit comments

Comments
 (0)