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

added a plus-minus-one range minimum query data structure #694

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
* [Infix To Postfix](https://github.com/TheAlgorithms/Rust/blob/master/src/data_structures/infix_to_postfix.rs)
* [Lazy Segment Tree](https://github.com/TheAlgorithms/Rust/blob/master/src/data_structures/lazy_segment_tree.rs)
* [Linked List](https://github.com/TheAlgorithms/Rust/blob/master/src/data_structures/linked_list.rs)
* [Plus-Minus-One Range Minimum Query](https://github.com/TheAlgorithms/Rust/blob/master/src/data_structures/plus_minus_rmq.rs)
* [Postfix Evaluation](https://github.com/TheAlgorithms/Rust/blob/master/src/data_structures/postfix_evaluation.rs)
* Probabilistic
* [Bloom Filter](https://github.com/TheAlgorithms/Rust/blob/master/src/data_structures/probabilistic/bloom_filter.rs)
Expand Down
2 changes: 2 additions & 0 deletions src/data_structures/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod heap;
mod infix_to_postfix;
mod lazy_segment_tree;
mod linked_list;
mod plus_minus_rmq;
mod postfix_evaluation;
mod probabilistic;
mod queue;
Expand All @@ -34,6 +35,7 @@ pub use self::heap::Heap;
pub use self::infix_to_postfix::infix_to_postfix;
pub use self::lazy_segment_tree::LazySegmentTree;
pub use self::linked_list::LinkedList;
pub use self::plus_minus_rmq::PlusMinusOneRMQ;
pub use self::postfix_evaluation::evaluate_postfix;
pub use self::probabilistic::bloom_filter;
pub use self::probabilistic::count_min_sketch;
Expand Down
204 changes: 204 additions & 0 deletions src/data_structures/plus_minus_rmq.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
use super::range_minimum_query::build_sparse_table;
use std::cmp::PartialOrd;

/// A data-structure for answering +-1 range minimum queries on arrays
///
/// # Complexity
/// Precomputation in O(n) and queries in O(1) time
///
/// # Notes
/// This is NOT the general RMQ on arrays but 'just' the +-1 RMQ, which is used in combination with LCA and
/// cartiesian trees on arrays to get a general RMQ implementation
///
/// # Sources
/// used <https://cp-algorithms.com/graph/lca_farachcoltonbender.html#implementation> as reference
pub struct PlusMinusOneRMQ<T: PartialOrd + Copy> {
array: Vec<T>,
k: usize,
block_min: Vec<T>,
block_min_idx: Vec<usize>,
sparse_idx: Vec<Vec<usize>>,
block_rmq: Vec<Vec<Vec<usize>>>,
block_mask: Vec<u32>,
}

impl<T: PartialOrd + Copy> PlusMinusOneRMQ<T> {
pub fn new(mut array: Vec<T>) -> Self {
input_padding(&mut array);
let k = (array.len().ilog2() / 2) as usize;
let mut new = Self {
array,
k,
block_min: Vec::new(),
block_min_idx: Vec::new(),
sparse_idx: vec![Vec::new()], // is a sparse table, which only stores the indeces
block_rmq: Vec::new(),
block_mask: Vec::new(),
};
new.calc_block_min();
new.sparse_idx = build_sparse_table(&new.array);
new.fill_block_rmq();
new.precompute_masks();

new
}
fn calc_block_min(&mut self) {
for i in 0..(self.array.len() + self.k - 1) / self.k {
let (min, min_idx) = self.calc_min(i * self.k);
self.block_min.push(min);
self.block_min_idx.push(min_idx)
}
}

fn calc_min(&mut self, i: usize) -> (T, usize) {
let mut current_min = self.array[i];
let mut min_idx: usize = i;
for j in i..i + self.k {
match self.array.get(j) {
Some(x) => {
current_min = min(current_min, *x);
min_idx = self.min_idx(min_idx, j);
}
None => break,
};
}
(current_min, min_idx)
}

pub fn get_range_min(&self, start: usize, end: usize) -> Result<T, &str> {
if start >= end || start >= self.array.len() || end > self.array.len() {
return Err("invalid range");
}

let block_l = start / self.k;
let block_r = (end - 1) / self.k;
let l_suffix = self.get_in_block(block_l, start % self.k, self.k - 1);
let r_prefix = self.get_in_block(block_r, 0, (end - 1) % self.k);
match block_r - block_l {
0 => Ok(self.array[self.get_in_block(block_l, start % self.k, (end - 1) % self.k)]),
1 => Ok(self.array[self.min_idx(l_suffix, r_prefix)]),
_ => Ok(self.array[self.min_idx(
self.min_idx(l_suffix, self.get_on_blocks(block_l + 1, block_r - 1)),
r_prefix,
)]),
}
}

fn get_on_blocks(&self, l: usize, r: usize) -> usize {
let loglen = (r - l + 1).ilog2() as usize;
let idx: usize = ((r as i64) - (1 << loglen as i64) + 1) as usize;
let a = self.sparse_idx[loglen][l];
let b = self.sparse_idx[loglen][idx];
self.min_idx(a, b)
}

fn get_in_block(&self, block_idx: usize, l: usize, r: usize) -> usize {
let mask = self.block_mask[block_idx];
let min_idx = self.block_rmq[mask as usize][l][r];
min_idx + block_idx * self.k
}

fn fill_block_rmq(&mut self) {
let mask_amount = 1 << (self.k - 1);
for mask in 0..mask_amount {
let tmp = self.rmq_bitmask(mask as u32); // maybe change to usize
self.block_rmq.push(tmp);
}
}

fn rmq_bitmask(&mut self, mask: u32) -> Vec<Vec<usize>> {
let mut rmq_matrix: Vec<Vec<usize>> = vec![vec![0; self.k]; self.k];
let list = bitmask_to_array(self.k, mask);
for i in 0..self.k {
for j in i..self.k {
if i == j {
rmq_matrix[i][j] = i;
} else {
let min = list[rmq_matrix[i][j - 1]]; //Do we want range-minimum or range-maximum
if list[j] < min {
rmq_matrix[i][j] = j;
} else {
rmq_matrix[i][j] = rmq_matrix[i][j - 1];
}
}
}
}
rmq_matrix
}

fn precompute_masks(&mut self) {
for i in 0..self.block_min.len() {
self.block_mask.push(self.calc_bitmask(i));
}
}

// we initialize the mask with k-1 ones
// this is necessary so if blocks are of size < k the bitmask is still correct
fn calc_bitmask(&self, block_idx: usize) -> u32 {
let mut mask: u32 = (1 << (self.k - 1)) - 1;
for i in self.k * block_idx + 1..self.k * (block_idx + 1) {
let last = self.array[i - 1];
match self.array.get(i) {
Some(&x) => {
if last >= x {
mask -= 1 << (self.k - 1 - (i % self.k));
}
}
None => break,
};
}
mask
}

fn min_idx(&self, i: usize, j: usize) -> usize {
if self.array[i] < self.array[j] {
return i;
}
j
}
}

// padds the given array to have at least length 4
// this is needed to have a valid k = 0.5 * log n
fn input_padding<T: Copy>(array: &mut Vec<T>) {
while array.len() < 4 {
let last = array[array.len() - 1];
array.push(last);
}
}

fn min<T: std::cmp::PartialOrd>(a: T, b: T) -> T {
match a < b {
true => a,
_ => b,
}
}

fn bitmask_to_array(k: usize, mut mask: u32) -> Vec<i32> {
let mut list: Vec<i32> = vec![0];
for i in 0..k - 1 {
match mask % 2 {
1 => list.push(list[i] - 1),
_ => list.push(list[i] + 1),
};
mask /= 2;
}
list.reverse();
list
}

#[cfg(test)]
mod tests {
#[test]
fn simple_query_tests() {
let v1 = vec![1, 2, 3, 2, 3, 4, 5, 4, 3, 2, 1, 0, -1];
let sparse_v1 = super::PlusMinusOneRMQ::new(v1);

assert_eq!(Ok(2), sparse_v1.get_range_min(1, 6));
assert_eq!(Ok(1), sparse_v1.get_range_min(0, 10));
assert_eq!(Ok(-1), sparse_v1.get_range_min(10, 13));
assert!(sparse_v1.get_range_min(4, 3).is_err());
assert!(sparse_v1.get_range_min(0, 1000).is_err());
assert!(sparse_v1.get_range_min(1000, 1001).is_err());
}
}
2 changes: 1 addition & 1 deletion src/data_structures/range_minimum_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl<T: PartialOrd + Copy> RangeMinimumQuery<T> {
}
}

fn build_sparse_table<T: PartialOrd>(array: &[T]) -> Vec<Vec<usize>> {
pub fn build_sparse_table<T: PartialOrd>(array: &[T]) -> Vec<Vec<usize>> {
let mut table: Vec<Vec<usize>> = vec![(0..array.len()).collect()];
let len = array.len();

Expand Down