Rust for Algorithmic Problem-Solving

Stack and Queue Patterns

Explicit stacks (Vec<T>) and queues (VecDeque<T>) are essential algorithmic building blocks for simulating recursion without overflowing the call stack, evaluating nested expressions, and processing multi-source wave-fronts level by level.

1. Explicit Stack (`Vec<Frame>`) Replaces Deep Recursion:
   Avoids OS 2 MiB thread stack overflow when tree/graph depth reaches $10^5 - 10^6$!

2. Multi-Source Level Queue (`VecDeque<(usize, usize)>`):
   Push all initial sources at distance 0, then process wave-front layer by layer!

1. Converting Deep Recursion into an Explicit Vec Stack

In competitive programming and production graph/AST analyzers, a skewed tree or chain graph of $N = 200,000$ vertices recurses $200,000$ stack frames deep—which overflows the standard 2 MiB–8 MiB OS call stack!

Replacing recursive function calls with an explicit heap-allocated Vec stack stores the pending work on the process heap (which can grow to gigabytes) in $O(1)$ call-stack space:

/// Evaluates a Reverse Polish Notation (RPN / Postfix) expression using a `Vec<i64>` stack.
pub fn eval_rpn(tokens: &[&str]) -> i64 {
    let mut stack: Vec<i64> = Vec::with_capacity(tokens.len());

    for &tok in tokens {
        match tok {
            "+" | "-" | "*" | "/" => {
                let right = stack.pop().expect("missing right operand");
                let left = stack.pop().expect("missing left operand");
                let res = match tok {
                    "+" => left + right,
                    "-" => left - right,
                    "*" => left * right,
                    "/" => left / right,
                    _ => unreachable!(),
                };
                stack.push(res);
            }
            num => stack.push(num.parse::<i64>().expect("invalid integer")),
        }
    }

    stack.pop().unwrap_or(0)
}

2. Multi-Source Wave-Front Propagation with VecDeque

When computing the minimum distance from every grid cell to the nearest source (such as nearest exit, nearest 0, or fire/water spread across a 2D grid), running a separate BFS from every cell takes $O((R \cdot C)^2)$ time.

Instead, push all source coordinates into a single VecDeque upfront at distance 0 and run one single multi-source BFS in $O(R \cdot C)$ linear time:

use std::collections::VecDeque;

/// Computes the Manhattan distance from every cell in an `R x C` grid to the nearest `0` cell
/// in O(R * C) time using a multi-source `VecDeque` wave-front.
pub fn distance_to_nearest_zero(grid: &[Vec<u8>]) -> Vec<Vec<u32>> {
    let rows = grid.len();
    let cols = grid[0].len();
    let mut dist = vec![vec![u32::MAX; cols]; rows];
    let mut queue = VecDeque::new();

    // 1. Seed the queue with ALL source cells (`0`) at distance 0 simultaneously:
    for r in 0..rows {
        for c in 0..cols {
            if grid[r][c] == 0 {
                dist[r][c] = 0;
                queue.push_back((r, c));
            }
        }
    }

    // Safe unsigned 4-directional grid neighbor offsets using `wrapping_add`:
    const DIRS: [(usize, usize); 4] = [(!0, 0), (1, 0), (0, !0), (0, 1)];

    // 2. Propagate wave-front outward layer by layer:
    while let Some((r, c)) = queue.pop_front() {
        let next_d = dist[r][c] + 1;
        for (dr, dc) in DIRS {
            let nr = r.wrapping_add(dr);
            let nc = c.wrapping_add(dc);
            if nr < rows && nc < cols && dist[nr][nc] > next_d {
                dist[nr][nc] = next_d;
                queue.push_back((nr, nc));
            }
        }
    }

    dist
}

fn main() {
    // (2 + 1) * 3 = 9
    let rpn_val = eval_rpn(&["2", "1", "+", "3", "*"]);
    assert_eq!(rpn_val, 9);

    let grid = vec![
        vec![0, 1, 1],
        vec![1, 1, 1],
        vec![1, 1, 0],
    ];
    let dists = distance_to_nearest_zero(&grid);
    assert_eq!(
        dists,
        vec![
            vec![0, 1, 2],
            vec![1, 2, 1],
            vec![2, 1, 0],
        ]
    );

    println!("RPN result: {rpn_val}, center cell distance to nearest 0: {}", dists[1][1]);
}
RPN result: 9, center cell distance to nearest 0: 2

Notice the unsigned 2D grid neighbor idiom let nr = r.wrapping_add(dr); if nr < rows: when r == 0 and dr == !0 (usize::MAX, representing -1), 0usize.wrapping_add(usize::MAX) wraps to usize::MAX, which is caught by the single unsigned check nr < rows without needing isize casts!