Writing

tgen::miniblog(3): Alias Method

2026-08-19 · Originally published on Codeforces

This is a blog 3 of a series of blogs about algorithmic challenges I came across when creating tgen.

In this blog we will tackle:

  1. Create a data structure with O(n)\mathcal O(n) time construction that can return i[0,n)i \in [0, n) with probability proportional to w[i]0w[i] \geq 0 in O(1)\mathcal O(1) time.

It is trivial to create such a data structure with Θ(logn)\Theta(\log n) query time: with a prefix sum array pp with values in [0,S][0, S], where S=iw[i]S = \sum_i w[i], just pick a uniformly random value x[0,S)x \in [0, S), and then do a binary search to find and return the first index ii such that p[i]>xp[i] \gt x.

Turns out that we can improve the query to O(1)\mathcal O(1) time, with linear construction. This is the alias method.

If all weights were the same, S/nS / n, then it would be trivial: just return next(0, n-1). If not, some indices are small (w[i]<S/nw[i] \lt S / n), and some are big (w[i]S/nw[i] \geq S / n). The idea is to “transfer” the exceeding probability of the small indices to the big ones.

To do that, after we choose a random index i = next(0, n-1), we will either return i with probability mass[i] / S, or return alias[i]. Note that there is no chaining, that is, we don’t follow many alias pointers; we either return i or alias[i]. Now we are left to show that it is always possible to choose an assignment for alias[i] and mass[i] in such a way that the probabilities are maintained.

To start, we scale every weight by nn to make everything an integer, so outcome ii has mass nw[i]n \cdot w[i]. The average scaled mass is exactly SS. Maintain two queues: small\mathrm{small} for indices with current mass <S\lt S, and big\mathrm{big} for indices with current mass S\geq S. While both are nonempty, pop ss from small\mathrm{small} and bb from big\mathrm{big}. Index ss is finalized: we keep mass[s]\mathrm{mass}[s] as the threshold for returning ss, and set alias[s]=b\mathrm{alias}[s] = b, so the leftover Smass[s]S - \mathrm{mass}[s] of the pick is given to bb. This leftover is at most SS (since mass[s]0\mathrm{mass}[s] \geq 0), and mass[b]S\mathrm{mass}[b] \geq S, so we never subtract more from bb than it has. After subtracting that leftover from mass[b]\mathrm{mass}[b], we put bb back into the appropriate queue (depending on whether its remaining mass is small or big). Each step permanently finalizes one index.

At the end, all indices remaining in big\mathrm{big} must have mass exactly SS (because the existence of an index with mass >S\gt S implies another with mass <S\lt S), so those indices are finalized as a single outcome (they are their own alias).

struct alias_method {
	int n;
	long long S;
	std::vector<long long> mass;
	std::vector<int> alias;

	alias_method(const std::vector<int>& w) : n(w.size()), alias(n) {
		S = 0;
		for (auto x : w) S += x;

		std::queue<int> small, big;
		for (int i = 0; i < n; i++) {
			mass.push_back(n * w[i]);
			if (mass[i] < S) small.push(i);
			else big.push(i);
		}

		while (!small.empty() and !big.empty()) {
			int s = small.front(); small.pop();
			int b = big.front(); big.pop();

			alias[s] = b;
			mass[b] -= S - mass[s];

			if (mass[b] < S) small.push(b);
			else big.push(b);
		}

		while (!big.empty()) {
			int b = big.front(); big.pop();
			alias[b] = b;
			mass[b] = S;
		}
	}
};

Algorithm 1: alias method construction.

To sample, pick a uniform index and compare a uniform sample in [0,S)[0, S) against the threshold stored in that index:

int next(const alias_method& a) {
	int i = next(0, a.n - 1);
	long long x = next(0, a.S - 1);
	return x < a.mass[i] ? i : a.alias[i];
}

Algorithm 2: alias method query.

Theorem 1: Algorithm 2 returns index ii with probability w[i]/Sw[i] / S.

Proof: Algorithm 2 picks an index jj uniformly at random, then returns jj if x<mass[j]x \lt \mathrm{mass}[j] and alias[j]\mathrm{alias}[j] otherwise, with xx uniform in [0,S)[0, S). So, conditioned on picking jj, we return jj with probability mass[j]/S\mathrm{mass}[j]/S and alias[j]\mathrm{alias}[j] with probability 1mass[j]/S1 - \mathrm{mass}[j]/S.

Initially mass[i]=nw[i]\mathrm{mass}[i] = n \cdot w[i]. Each pairing of a small index ss with a big index bb keeps mass[s]\mathrm{mass}[s] units with ss and moves Smass[s]S - \mathrm{mass}[s] units from bb to the alias of ss.

The probability of returning ii is therefore

1nmass[i]S+alias[j]=i1nSmass[j]S=AinS,\frac{1}{n} \cdot \frac{\mathrm{mass}[i]}{S} + \sum_{\mathrm{alias}[j] = i} \frac{1}{n} \cdot \frac{S - \mathrm{mass}[j]}{S} = \frac{A_i}{nS},

where Ai=mass[i]+alias[j]=i(Smass[j])A_i = \mathrm{mass}[i] + \sum_{\mathrm{alias}[j] = i} (S - \mathrm{mass}[j]). The first term is the case where we pick ii and keep it; each term in the sum is the case where we pick some jj and take the alias ii.

Initially Ai=nw[i]A_i = n \cdot w[i], since there are no aliases yet. A pairing does not change AsA_s or AbA_b. We do not change mass[s]\mathrm{mass}[s], so AsA_s stays the same. For AbA_b, we subtract Smass[s]S - \mathrm{mass}[s] from mass[b]\mathrm{mass}[b], but we also set alias[s]=b\mathrm{alias}[s] = b, which adds the same Smass[s]S - \mathrm{mass}[s] to the sum in AbA_b. Thus Ai=nw[i]A_i = n \cdot w[i] at the end, so we get the final probability of returning ii:

AinS=nw[i]nS=w[i]S.\frac{A_i}{nS} = \frac{n \cdot w[i]}{nS} = \frac{w[i]}{S}. \square

References

Alias method (Wikipedia): https://en.wikipedia.org/wiki/Alias_method

Keith Schwarz’s exposition: https://www.keithschwarz.com/darts-dice-coins/