ExamplesBy LevelBy TopicLearning Paths
848 Advanced

Algorithm Complexity Guide

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Algorithm Complexity Guide" functional Rust example. Difficulty level: Advanced. Key concepts covered: Functional Programming. Understanding time and space complexity is essential for writing software that scales. Key difference from OCaml: | Aspect | Rust | OCaml |

Tutorial

The Problem

Understanding time and space complexity is essential for writing software that scales. A function that runs in 1ms for n=100 might take 10 seconds for n=10,000 if it's O(n^2) — and 3 years for n=1,000,000. Engineers need an intuitive grasp of which complexity class their code falls into, and which operations trigger which class. This reference guide concretizes the abstract Big-O notation with real Rust examples: constant-time array access, logarithmic binary search, linear scans, O(n log n) sorting, quadratic nested loops, exponential backtracking. Each example demonstrates not just the code but why it achieves its complexity class.

🎯 Learning Outcomes

  • • Recognize O(1), O(log n), O(n), O(n log n), O(n^2), O(n^3), O(2^n), O(n!) from code structure
  • • Understand how recursion depth, loop nesting, and problem halving determine complexity
  • • Apply Master Theorem to divide-and-conquer recurrences: T(n) = aT(n/b) + f(n)
  • • Calculate the practical limit for each complexity class: O(n^2) for n=10,000 (~10^8 ops) is borderline
  • • Recognize amortized complexity: Vec push is O(1) amortized despite occasional O(n) reallocation
  • Code Example

    #![allow(clippy::all)]
    //! # Algorithm Complexity Guide
    //!
    //! Reference for common algorithm complexities.
    
    /// O(1) - Constant time
    pub fn constant_time_example(arr: &[i32]) -> Option<i32> {
        arr.first().copied()
    }
    
    /// O(log n) - Logarithmic time
    pub fn binary_search(arr: &[i32], target: i32) -> Option<usize> {
        arr.binary_search(&target).ok()
    }
    
    /// O(n) - Linear time
    pub fn linear_search(arr: &[i32], target: i32) -> Option<usize> {
        arr.iter().position(|&x| x == target)
    }
    
    /// O(n log n) - Linearithmic time
    pub fn merge_sort(mut arr: Vec<i32>) -> Vec<i32> {
        if arr.len() <= 1 {
            return arr;
        }
        let mid = arr.len() / 2;
        let right = merge_sort(arr.split_off(mid));
        let left = merge_sort(arr);
        merge(left, right)
    }
    
    fn merge(a: Vec<i32>, b: Vec<i32>) -> Vec<i32> {
        let mut result = Vec::with_capacity(a.len() + b.len());
        let (mut i, mut j) = (0, 0);
        while i < a.len() && j < b.len() {
            if a[i] <= b[j] {
                result.push(a[i]);
                i += 1;
            } else {
                result.push(b[j]);
                j += 1;
            }
        }
        result.extend_from_slice(&a[i..]);
        result.extend_from_slice(&b[j..]);
        result
    }
    
    /// O(n²) - Quadratic time
    pub fn bubble_sort(arr: &mut [i32]) {
        let n = arr.len();
        for i in 0..n {
            for j in 0..n - i - 1 {
                if arr[j] > arr[j + 1] {
                    arr.swap(j, j + 1);
                }
            }
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_constant() {
            assert_eq!(constant_time_example(&[1, 2, 3]), Some(1));
        }
        #[test]
        fn test_binary() {
            assert_eq!(binary_search(&[1, 2, 3, 4, 5], 3), Some(2));
        }
        #[test]
        fn test_linear() {
            assert_eq!(linear_search(&[1, 2, 3, 4, 5], 3), Some(2));
        }
        #[test]
        fn test_merge() {
            assert_eq!(merge_sort(vec![3, 1, 4, 1, 5, 9]), vec![1, 1, 3, 4, 5, 9]);
        }
    }

    Key Differences

    AspectRustOCaml
    len()Vec::len() is O(1)List.length is O(n), Array.length is O(1)
    Indexingslice[i] is O(1)array.(i) is O(1), List.nth is O(n)
    Sortslice.sort() is O(n log n)List.sort is O(n log n)
    HashMapO(1) amortized insert/lookupHashtbl O(1) average
    BTreeMapO(log n) insert/lookupMap.t O(log n)
    Stack overflowHappens at ~8KB stack depthTCO prevents for tail calls

    OCaml Approach

    OCaml's complexity guide mirrors Rust's: List.hd for O(1), List.nth for O(n) (lists are not random-access!), array .(i) for O(1), Array.binary_search for O(log n). OCaml's lazy Seq enables O(1) per-element consumption of infinite sequences. List.length is O(n) in OCaml (not cached), unlike Rust's Vec::len() which is O(1). This highlights a critical difference: OCaml lists don't cache length; Array.length is O(1). Understanding the OCaml stdlib's complexity is essential for writing efficient OCaml.

    Full Source

    #![allow(clippy::all)]
    //! # Algorithm Complexity Guide
    //!
    //! Reference for common algorithm complexities.
    
    /// O(1) - Constant time
    pub fn constant_time_example(arr: &[i32]) -> Option<i32> {
        arr.first().copied()
    }
    
    /// O(log n) - Logarithmic time
    pub fn binary_search(arr: &[i32], target: i32) -> Option<usize> {
        arr.binary_search(&target).ok()
    }
    
    /// O(n) - Linear time
    pub fn linear_search(arr: &[i32], target: i32) -> Option<usize> {
        arr.iter().position(|&x| x == target)
    }
    
    /// O(n log n) - Linearithmic time
    pub fn merge_sort(mut arr: Vec<i32>) -> Vec<i32> {
        if arr.len() <= 1 {
            return arr;
        }
        let mid = arr.len() / 2;
        let right = merge_sort(arr.split_off(mid));
        let left = merge_sort(arr);
        merge(left, right)
    }
    
    fn merge(a: Vec<i32>, b: Vec<i32>) -> Vec<i32> {
        let mut result = Vec::with_capacity(a.len() + b.len());
        let (mut i, mut j) = (0, 0);
        while i < a.len() && j < b.len() {
            if a[i] <= b[j] {
                result.push(a[i]);
                i += 1;
            } else {
                result.push(b[j]);
                j += 1;
            }
        }
        result.extend_from_slice(&a[i..]);
        result.extend_from_slice(&b[j..]);
        result
    }
    
    /// O(n²) - Quadratic time
    pub fn bubble_sort(arr: &mut [i32]) {
        let n = arr.len();
        for i in 0..n {
            for j in 0..n - i - 1 {
                if arr[j] > arr[j + 1] {
                    arr.swap(j, j + 1);
                }
            }
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_constant() {
            assert_eq!(constant_time_example(&[1, 2, 3]), Some(1));
        }
        #[test]
        fn test_binary() {
            assert_eq!(binary_search(&[1, 2, 3, 4, 5], 3), Some(2));
        }
        #[test]
        fn test_linear() {
            assert_eq!(linear_search(&[1, 2, 3, 4, 5], 3), Some(2));
        }
        #[test]
        fn test_merge() {
            assert_eq!(merge_sort(vec![3, 1, 4, 1, 5, 9]), vec![1, 1, 3, 4, 5, 9]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_constant() {
            assert_eq!(constant_time_example(&[1, 2, 3]), Some(1));
        }
        #[test]
        fn test_binary() {
            assert_eq!(binary_search(&[1, 2, 3, 4, 5], 3), Some(2));
        }
        #[test]
        fn test_linear() {
            assert_eq!(linear_search(&[1, 2, 3, 4, 5], 3), Some(2));
        }
        #[test]
        fn test_merge() {
            assert_eq!(merge_sort(vec![3, 1, 4, 1, 5, 9]), vec![1, 1, 3, 4, 5, 9]);
        }
    }

    Deep Comparison

    OCaml vs Rust: Algorithm Complexity Guide

    Overview

    See the example.rs and example.ml files for detailed implementations.

    Key Differences

    AspectOCamlRust
    Type systemHindley-MilnerOwnership + traits
    MemoryGCZero-cost abstractions
    MutabilityExplicit refmut keyword
    Error handlingOption/ResultResult<T, E>

    See README.md for detailed comparison.

    Exercises

  • Write a benchmark that empirically measures sorting time for n=100,1000,10000,100000 and verify O(n log n) by fitting a curve.
  • Find an example in this codebase where the actual complexity differs from what the code appears to be.
  • Analyze the amortized complexity of Vec::push: prove that n pushes cost O(n) total despite occasional O(n) copies.
  • Implement and benchmark a function with O(n^3) complexity and find the practical n where it exceeds 1 second.
  • Explain why HashMap::get is O(1) amortized but has O(n) worst case, and when worst case can be triggered.
  • Open Source Repos