Unit 6: Randomization

Randomized Quicksort, Randomized Select, Skip Lists

Duration: 6 Hours

Learning Objectives

1. Randomized Quicksort

Randomized quicksort uses a random pivot selection to achieve expected O(n log n) time complexity, avoiding worst-case O(n²) performance on sorted inputs.

Advantages of Randomization:

  • Expected Performance: O(n log n) regardless of input
  • Worst-case Avoidance: No adversarial inputs
  • Simple Implementation: Just randomize pivot selection
  • Cache Efficiency: Good locality of reference

Randomized Quicksort Visualization

Watch how random pivot selection affects sorting:

Enter array elements and click Start Sort

Randomized Quicksort Implementation

#include 
#include 
#include 
#include 

class RandomizedQuickSort {
private:
    static std::mt19937 rng;
    
public:
    static void sort(std::vector& arr) {
        quicksort(arr, 0, arr.size() - 1);
    }
    
private:
    static void quicksort(std::vector& arr, int low, int high) {
        if (low < high) {
            int pivotIndex = randomizedPartition(arr, low, high);
            quicksort(arr, low, pivotIndex - 1);
            quicksort(arr, pivotIndex + 1, high);
        }
    }
    
    static int randomizedPartition(std::vector& arr, int low, int high) {
        // Generate random index between low and high
        std::uniform_int_distribution dist(low, high);
        int randomIndex = dist(rng);
        
        // Swap random element with last element
        std::swap(arr[randomIndex], arr[high]);
        
        // Perform standard partition with last element as pivot
        return partition(arr, low, high);
    }
    
    static int partition(std::vector& arr, int low, int high) {
        int pivot = arr[high];
        int i = low - 1;
        
        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                std::swap(arr[i], arr[j]);
            }
        }
        
        std::swap(arr[i + 1], arr[high]);
        return i + 1;
    }
    
public:
    // Three-way partition for handling duplicates efficiently
    static void dutchFlagSort(std::vector& arr, int low, int high) {
        if (low >= high) return;
        
        auto [lt, gt] = threeWayPartition(arr, low, high);
        dutchFlagSort(arr, low, lt - 1);
        dutchFlagSort(arr, gt + 1, high);
    }
    
private:
    static std::pair threeWayPartition(std::vector& arr, int low, int high) {
        std::uniform_int_distribution dist(low, high);
        int randomIndex = dist(rng);
        std::swap(arr[randomIndex], arr[low]);
        
        int pivot = arr[low];
        int lt = low;      // arr[low..lt-1] < pivot
        int i = low + 1;   // arr[lt..i-1] == pivot
        int gt = high + 1; // arr[gt..high] > pivot
        
        while (i < gt) {
            if (arr[i] < pivot) {
                std::swap(arr[lt++], arr[i++]);
            } else if (arr[i] > pivot) {
                std::swap(arr[i], arr[--gt]);
            } else {
                i++;
            }
        }
        
        return {lt, gt};
    }
    
public:
    // Performance analysis
    static void benchmark(std::vector arr, const std::string& description) {
        auto start = std::chrono::high_resolution_clock::now();
        sort(arr);
        auto end = std::chrono::high_resolution_clock::now();
        
        auto duration = std::chrono::duration_cast(end - start);
        std::cout << description << ": " << duration.count() << " microseconds" << std::endl;
    }
};

// Initialize random number generator
std::mt19937 RandomizedQuickSort::rng(std::chrono::steady_clock::now().time_since_epoch().count());

2. Randomized Selection

Randomized selection finds the k-th smallest element in expected O(n) time using random pivot selection.

Randomized Select Algorithm

1 If array has one element, return it
2 Choose random pivot and partition array
3 If pivot position equals k, return pivot
4 Recursively search left or right subarray

Randomized Selection Visualization

Find the k-th smallest element using randomized selection:

Enter array and k value to find k-th smallest element

Randomized Selection Implementation

#include 
#include 
#include 
#include 

class RandomizedSelect {
private:
    static std::mt19937 rng;
    
public:
    // Find k-th smallest element (1-indexed)
    static int select(std::vector& arr, int k) {
        if (k < 1 || k > arr.size()) {
            throw std::invalid_argument("k out of range");
        }
        return quickSelect(arr, 0, arr.size() - 1, k - 1); // Convert to 0-indexed
    }
    
private:
    static int quickSelect(std::vector& arr, int low, int high, int k) {
        if (low == high) {
            return arr[low];
        }
        
        int pivotIndex = randomizedPartition(arr, low, high);
        
        if (k == pivotIndex) {
            return arr[k];
        } else if (k < pivotIndex) {
            return quickSelect(arr, low, pivotIndex - 1, k);
        } else {
            return quickSelect(arr, pivotIndex + 1, high, k);
        }
    }
    
    static int randomizedPartition(std::vector& arr, int low, int high) {
        std::uniform_int_distribution dist(low, high);
        int randomIndex = dist(rng);
        std::swap(arr[randomIndex], arr[high]);
        return partition(arr, low, high);
    }
    
    static int partition(std::vector& arr, int low, int high) {
        int pivot = arr[high];
        int i = low - 1;
        
        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                std::swap(arr[i], arr[j]);
            }
        }
        
        std::swap(arr[i + 1], arr[high]);
        return i + 1;
    }
    
public:
    // Find k smallest elements
    static std::vector selectMultiple(std::vector arr, int k) {
        if (k <= 0 || k > arr.size()) {
            return {};
        }
        
        // Use quickselect to partition around k-th element
        int kthElement = select(arr, k);
        
        // Collect all elements <= k-th element
        std::vector result;
        int count = 0;
        
        for (int x : arr) {
            if (x < kthElement || (x == kthElement && count < k)) {
                result.push_back(x);
                if (x == kthElement) count++;
            }
        }
        
        return result;
    }
    
    // Find median using randomized select
    static double findMedian(std::vector arr) {
        int n = arr.size();
        if (n % 2 == 1) {
            return select(arr, (n + 1) / 2);
        } else {
            int lower = select(arr, n / 2);
            int upper = select(arr, n / 2 + 1);
            return (lower + upper) / 2.0;
        }
    }
    
    // Iterative version to avoid stack overflow
    static int selectIterative(std::vector& arr, int k) {
        if (k < 1 || k > arr.size()) {
            throw std::invalid_argument("k out of range");
        }
        
        int low = 0, high = arr.size() - 1;
        k--; // Convert to 0-indexed
        
        while (low < high) {
            int pivotIndex = randomizedPartition(arr, low, high);
            
            if (k == pivotIndex) {
                return arr[k];
            } else if (k < pivotIndex) {
                high = pivotIndex - 1;
            } else {
                low = pivotIndex + 1;
            }
        }
        
        return arr[low];
    }
};

std::mt19937 RandomizedSelect::rng(std::chrono::steady_clock::now().time_since_epoch().count());

3. Skip Lists

Skip lists are probabilistic data structures that provide O(log n) expected time for search, insertion, and deletion operations.

Skip List Properties:

  • Probabilistic Structure: Random tower heights
  • Multiple Levels: Express lanes for faster traversal
  • Expected Performance: O(log n) for all operations
  • Simple Implementation: Easier than balanced trees

Skip List Visualization

Build and search a skip list structure:

Insert values to build the skip list

Skip List Implementation

#include 
#include 
#include 
#include 

class SkipListNode {
public:
    int value;
    std::vector forward;
    
    SkipListNode(int val, int level) : value(val), forward(level + 1, nullptr) {}
};

class SkipList {
private:
    static const int MAX_LEVEL = 16;
    static const double P = 0.5; // Probability for coin flip
    
    SkipListNode* header;
    int level;
    std::mt19937 rng;
    std::uniform_real_distribution dist;
    
public:
    SkipList() : level(0), rng(std::random_device{}()), dist(0.0, 1.0) {
        header = new SkipListNode(INT_MIN, MAX_LEVEL);
    }
    
    ~SkipList() {
        SkipListNode* current = header;
        while (current) {
            SkipListNode* next = current->forward[0];
            delete current;
            current = next;
        }
    }
    
private:
    int randomLevel() {
        int lvl = 0;
        while (dist(rng) < P && lvl < MAX_LEVEL) {
            lvl++;
        }
        return lvl;
    }
    
public:
    void insert(int value) {
        std::vector update(MAX_LEVEL + 1);
        SkipListNode* current = header;
        
        // Search for insertion point
        for (int i = level; i >= 0; i--) {
            while (current->forward[i] && current->forward[i]->value < value) {
                current = current->forward[i];
            }
            update[i] = current;
        }
        
        current = current->forward[0];
        
        // If value doesn't exist, insert it
        if (!current || current->value != value) {
            int newLevel = randomLevel();
            
            // If new level is greater than current level
            if (newLevel > level) {
                for (int i = level + 1; i <= newLevel; i++) {
                    update[i] = header;
                }
                level = newLevel;
            }
            
            // Create new node
            SkipListNode* newNode = new SkipListNode(value, newLevel);
            
            // Update forward pointers
            for (int i = 0; i <= newLevel; i++) {
                newNode->forward[i] = update[i]->forward[i];
                update[i]->forward[i] = newNode;
            }
        }
    }
    
    bool search(int value) {
        SkipListNode* current = header;
        
        // Start from highest level
        for (int i = level; i >= 0; i--) {
            while (current->forward[i] && current->forward[i]->value < value) {
                current = current->forward[i];
            }
        }
        
        current = current->forward[0];
        return current && current->value == value;
    }
    
    bool remove(int value) {
        std::vector update(MAX_LEVEL + 1);
        SkipListNode* current = header;
        
        // Search for node to delete
        for (int i = level; i >= 0; i--) {
            while (current->forward[i] && current->forward[i]->value < value) {
                current = current->forward[i];
            }
            update[i] = current;
        }
        
        current = current->forward[0];
        
        // If found, delete it
        if (current && current->value == value) {
            // Update forward pointers
            for (int i = 0; i <= level; i++) {
                if (update[i]->forward[i] != current) {
                    break;
                }
                update[i]->forward[i] = current->forward[i];
            }
            
            delete current;
            
            // Update level
            while (level > 0 && !header->forward[level]) {
                level--;
            }
            
            return true;
        }
        
        return false;
    }
    
    void display() {
        std::cout << "Skip List:" << std::endl;
        for (int i = level; i >= 0; i--) {
            std::cout << "Level " << i << ": ";
            SkipListNode* current = header->forward[i];
            while (current) {
                std::cout << current->value << " ";
                current = current->forward[i];
            }
            std::cout << std::endl;
        }
    }
    
    // Get all values in sorted order
    std::vector getValues() {
        std::vector values;
        SkipListNode* current = header->forward[0];
        while (current) {
            values.push_back(current->value);
            current = current->forward[0];
        }
        return values;
    }
    
    // Range query: find all values in [low, high]
    std::vector rangeQuery(int low, int high) {
        std::vector result;
        SkipListNode* current = header;
        
        // Find starting position
        for (int i = level; i >= 0; i--) {
            while (current->forward[i] && current->forward[i]->value < low) {
                current = current->forward[i];
            }
        }
        
        current = current->forward[0];
        
        // Collect values in range
        while (current && current->value <= high) {
            result.push_back(current->value);
            current = current->forward[0];
        }
        
        return result;
    }
};

4. Probabilistic Analysis

Expected Performance Analysis:

  • Randomized Quicksort: E[T(n)] = O(n log n)
  • Randomized Select: E[T(n)] = O(n)
  • Skip List Height: E[height] = O(log n)
  • Skip List Operations: E[time] = O(log n)

Probability Visualization

Random Number Generation

Visualize probability distributions:

Click buttons to visualize random distributions

5. Advanced Randomization Techniques

#include 
#include 
#include 
#include 

class AdvancedRandomization {
public:
    // Reservoir sampling: randomly sample k items from stream
    static std::vector reservoirSample(const std::vector& stream, int k) {
        if (k <= 0 || stream.empty()) return {};
        
        std::vector reservoir;
        std::mt19937 rng(std::random_device{}());
        
        // Fill reservoir with first k elements
        for (int i = 0; i < std::min(k, (int)stream.size()); i++) {
            reservoir.push_back(stream[i]);
        }
        
        // Process remaining elements
        for (int i = k; i < stream.size(); i++) {
            std::uniform_int_distribution dist(0, i);
            int j = dist(rng);
            
            if (j < k) {
                reservoir[j] = stream[i];
            }
        }
        
        return reservoir;
    }
    
    // Fisher-Yates shuffle
    static void fisherYatesShuffle(std::vector& arr) {
        std::mt19937 rng(std::random_device{}());
        
        for (int i = arr.size() - 1; i > 0; i--) {
            std::uniform_int_distribution dist(0, i);
            int j = dist(rng);
            std::swap(arr[i], arr[j]);
        }
    }
    
    // Generate random permutation
    static std::vector randomPermutation(int n) {
        std::vector perm(n);
        std::iota(perm.begin(), perm.end(), 0);
        fisherYatesShuffle(perm);
        return perm;
    }
    
    // Monte Carlo algorithm: estimate π
    static double estimatePi(int numSamples) {
        std::mt19937 rng(std::random_device{}());
        std::uniform_real_distribution dist(-1.0, 1.0);
        
        int insideCircle = 0;
        
        for (int i = 0; i < numSamples; i++) {
            double x = dist(rng);
            double y = dist(rng);
            
            if (x * x + y * y <= 1.0) {
                insideCircle++;
            }
        }
        
        return 4.0 * insideCircle / numSamples;
    }
    
    // Las Vegas algorithm: randomized primality test
    static bool millerRabinPrimality(long long n, int k = 5) {
        if (n < 2) return false;
        if (n == 2 || n == 3) return true;
        if (n % 2 == 0) return false;
        
        // Write n-1 as d * 2^r
        long long d = n - 1;
        int r = 0;
        while (d % 2 == 0) {
            d /= 2;
            r++;
        }
        
        std::mt19937 rng(std::random_device{}());
        
        for (int i = 0; i < k; i++) {
            std::uniform_int_distribution dist(2, n - 2);
            long long a = dist(rng);
            
            long long x = modPow(a, d, n);
            if (x == 1 || x == n - 1) continue;
            
            bool composite = true;
            for (int j = 0; j < r - 1; j++) {
                x = (x * x) % n;
                if (x == n - 1) {
                    composite = false;
                    break;
                }
            }
            
            if (composite) return false;
        }
        
        return true;
    }
    
private:
    static long long modPow(long long base, long long exp, long long mod) {
        long long result = 1;
        while (exp > 0) {
            if (exp % 2 == 1) {
                result = (result * base) % mod;
            }
            base = (base * base) % mod;
            exp /= 2;
        }
        return result;
    }
};

6. Performance Comparison

Algorithm Worst Case Expected Case Advantage
Deterministic Quicksort O(n²) O(n log n) Simple, cache-friendly
Randomized Quicksort O(n²) O(n log n) No adversarial input
Median-of-medians Select O(n) O(n) Guaranteed linear
Randomized Select O(n²) O(n) Simple, fast in practice
Balanced BST O(log n) O(log n) Guaranteed performance
Skip List O(n) O(log n) Simple implementation

7. Complete Example Program

#include 

int main() {
    std::cout << "=== Unit 6: Randomization Demo ===" << std::endl;
    
    // Randomized Quicksort Demo
    std::cout << "\n--- Randomized Quicksort ---" << std::endl;
    std::vector arr = {64, 34, 25, 12, 22, 11, 90, 88, 76, 50, 42};
    
    std::cout << "Original array: ";
    for (int x : arr) std::cout << x << " ";
    std::cout << std::endl;
    
    RandomizedQuickSort::sort(arr);
    
    std::cout << "Sorted array: ";
    for (int x : arr) std::cout << x << " ";
    std::cout << std::endl;
    
    // Randomized Selection Demo
    std::cout << "\n--- Randomized Selection ---" << std::endl;
    std::vector selectArr = {3, 6, 8, 10, 1, 2, 1};
    
    std::cout << "Array: ";
    for (int x : selectArr) std::cout << x << " ";
    std::cout << std::endl;
    
    int k = 3;
    int kthSmallest = RandomizedSelect::select(selectArr, k);
    std::cout << k << "-th smallest element: " << kthSmallest << std::endl;
    
    double median = RandomizedSelect::findMedian(selectArr);
    std::cout << "Median: " << median << std::endl;
    
    // Skip List Demo
    std::cout << "\n--- Skip List ---" << std::endl;
    SkipList skipList;
    
    std::vector values = {3, 6, 7, 9, 12, 19, 17, 26, 21, 25};
    for (int val : values) {
        skipList.insert(val);
        std::cout << "Inserted " << val << std::endl;
    }
    
    skipList.display();
    
    std::cout << "Search 17: " << (skipList.search(17) ? "Found" : "Not found") << std::endl;
    std::cout << "Search 20: " << (skipList.search(20) ? "Found" : "Not found") << std::endl;
    
    std::vector range = skipList.rangeQuery(10, 20);
    std::cout << "Range query [10, 20]: ";
    for (int x : range) std::cout << x << " ";
    std::cout << std::endl;
    
    // Advanced Randomization Demo
    std::cout << "\n--- Advanced Randomization ---" << std::endl;
    
    // Estimate π
    double piEstimate = AdvancedRandomization::estimatePi(100000);
    std::cout << "Estimated π: " << piEstimate << std::endl;
    
    // Reservoir sampling
    std::vector stream = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector sample = AdvancedRandomization::reservoirSample(stream, 3);
    std::cout << "Reservoir sample (k=3): ";
    for (int x : sample) std::cout << x << " ";
    std::cout << std::endl;
    
    return 0;
}