Table of Contents
816 words
4 minutes
Randomized Algorithms: Shuffling, Reservoir Sampling, and Monte Carlo

Overview#

Randomized Algorithms use random numbers to make choices, sample, or approximate estimates. They are commonly used to shuffle order, uniformly sample from a data stream, distribute traffic by weight, and estimate complex results through random sampling.

Prerequisites

  • Probability fundamentals: understanding uniform and weighted probability
  • Prefix sums / binary search: for efficient weighted random selection
  • Hash functions: for stable traffic splitting, rather than being fully random each time

Problem Definition#

In scenarios involving uncertainty, design algorithms that satisfy probabilistic requirements while controlling time, space, and stability.

ElementDescription
InputArray, data stream, weight table, or number of samples
OutputRandom permutation, random sample, weighted result, or approximate value
Core requirementsProbabilistically correct, controllable complexity, reproducible when necessary
Typical algorithmsFisher-Yates, reservoir sampling, weighted random, Monte Carlo

Core Principle: Step-by-Step Illustration#

Fisher-Yates shuffle fixes positions from back to front, each time selecting an element from [0, i] with equal probability to swap:

Array to shuffle Start from the last position Randomly pick 0..i Swap into position i Move i left

Because the i-th position is chosen with equal probability from the remaining i + 1 elements, every permutation has an equal probability of occurring.


Detailed Algorithm Steps#

Algorithm: FisherYatesShuffle(arr)
Input: array arr
Output: the array shuffled in place
1. for i from arr.length - 1 downto 1:
2. j ← random integer in [0, i]
3. swap arr[i] and arr[j]
4. return arr

Complexity analysis:

AlgorithmTime complexitySpace complexityUse case
Fisher-YatesO(n)O(1)Uniform random permutation
Reservoir samplingO(n)O(k)Sampling from data streams of unknown length
Weighted random linear versionO(n)O(1)Few selections
Prefix sum + binary searchO(log n) / opO(n)Multiple weighted selections
Monte CarloO(samples)O(1)Approximate estimation

TypeScript Implementation#

1. Fisher-Yates Shuffle#

function shuffle<T>(arr: T[]): T[] {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}

2. Reservoir Sampling#

function reservoirSampling<T>(stream: T[], k: number): T[] {
const reservoir: T[] = [];
for (let i = 0; i < stream.length; i++) {
if (i < k) {
reservoir.push(stream[i]);
} else {
const j = Math.floor(Math.random() * (i + 1));
if (j < k) reservoir[j] = stream[i];
}
}
return reservoir;
}

3. Weighted Random Selection#

interface WeightedItem<T> {
value: T;
weight: number;
}
function weightedRandom<T>(items: WeightedItem<T>[]): T {
const total = items.reduce((sum, item) => sum + item.weight, 0);
let random = Math.random() * total;
for (const item of items) {
random -= item.weight;
if (random <= 0) return item.value;
}
return items[items.length - 1].value;
}

4. Prefix Sum + Binary Search Optimization#

class WeightedPicker<T> {
private values: T[];
private prefix: number[] = [];
private total = 0;
constructor(items: WeightedItem<T>[]) {
this.values = items.map(item => item.value);
for (const item of items) {
this.total += item.weight;
this.prefix.push(this.total);
}
}
pick(): T {
const target = Math.random() * this.total;
let left = 0;
let right = this.prefix.length - 1;
while (left < right) {
const mid = left + Math.floor((right - left) / 2);
if (this.prefix[mid] < target) left = mid + 1;
else right = mid;
}
return this.values[left];
}
}

5. Monte Carlo Estimation of π#

function estimatePi(samples = 1_000_000): number {
let inside = 0;
for (let i = 0; i < samples; i++) {
const x = Math.random();
const y = Math.random();
if (x * x + y * y <= 1) inside++;
}
return (inside / samples) * 4;
}

6. Stable A/B Splitting#

class ABTesting {
private variants: string[];
private weights: number[];
constructor(config: Record<string, number>) {
this.variants = Object.keys(config);
this.weights = Object.values(config);
}
assign(userId: string): string {
const total = this.weights.reduce((sum, weight) => sum + weight, 0);
const bucket = this.hashCode(userId) % total;
let current = 0;
for (let i = 0; i < this.variants.length; i++) {
current += this.weights[i];
if (bucket < current) return this.variants[i];
}
return this.variants[this.variants.length - 1];
}
private hashCode(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0;
}
return Math.abs(hash);
}
}

Engineering Optimization: Randomness, Security, and Reproducibility#

ScenarioRecommended approachReason
Ordinary UI randomnessMath.random()Simple and sufficient
Security-sensitive randomnesscrypto.getRandomValues()Math.random() is not suitable for security purposes
A/B testingHash user IDEnsures the same user is consistently grouped
Testing randomized algorithmsInject random number generatorFacilitates reproduction and unit testing
Multiple weighted selectionsPrefix sum + binary searchO(log n) per operation after preprocessing

The correctness of randomized algorithms is usually probabilistic, and a single result should not be used to judge whether an implementation is correct.


Applications and Limitations#

Typical Applications#

  • List shuffling, random playback, lotteries
  • Log stream and data stream sampling
  • A/B testing and gradual rollout
  • Monte Carlo estimation, randomized QuickSelect

Limitations#

LimitationDescription
Unstable resultsThe random source must be controlled when reproducibility is required
Difficult probability verificationRequires statistical testing rather than a single example
Different security requirementsBusiness randomness and cryptographic randomness cannot be mixed

Summary#

Clarify the probability goal Choose a random model Implement sampling or branching Verify the distribution Handle reproducibility and security requirements

Key points:

  1. Fisher-Yates is the standard algorithm for uniform shuffling.
  2. Reservoir sampling is suitable for data streams of unknown length.
  3. Multiple weighted selections can be optimized with prefix sums and binary search.
  4. Do not use Math.random() in security scenarios.
Randomized Algorithms: Shuffling, Reservoir Sampling, and Monte Carlo
https://www.hehonglei.com/technology/randomized-algorithms/
Author
HHLBlog
Published at
2026-04-26
License
CC BY-NC-SA 4.0