一个集合里有 n 个元素,每个元素有不同的权重,现在要不放回地随机抽取 m 个元素,每个元素被抽中的概率为元素的权重占总权重的比例。要怎么做呢?
现在考虑只抽取一个元素,假设权重之和为 1 。我们可以从 [0, 1] 中随机得到一个权重,假设为 0.71 ,而后从第一个元素开始,不断累加它们的权重,直到有一个元素的累加权重包含 0.71 ,则选取该元素。下面是个示意图:
要选取 m 个元素,则可以按上面的方法先选取一个,将该元素从集合中去除,再反复按上面的方法抽取剩余的元素。这种方法的复杂度是 O(mn) ,并且将元素从集合中删除其实不太方便实现。
当然,最重要的是这个算法需要多次遍历数据,不适合用在流处理的场景中。
Algorithm A 是论文 Weighted Random Sampling 中提出的,步骤如下:
对于集合 $V$ 中的元素 $v_i \in V$,选取均匀分布的随机数 $u_i = rand(0, 1)$ ,计算元素的特征 $k_i = u_i^{(1/w_i)}$
将集合按 $k_i$ 排序,选取前 $m$ 大的元素。
算法的正确性在作者 2006 年的论文 Weighted random sampling with a reservoir 里给了详细的证明。论文中给出了算法的两个变种 A-Res 与 A-ExpJ,它们都能在一次扫描中得到 m 个样本。非常适合在流处理的场合中。
A-Res(Algorithm A With a Reservoir) 是 Algorithm 的“蓄水池”版本,即维护含有 m 个元素的结果集,对每个新元素尝试去替换结果集中权重最小的元素。步骤如下:
将集合 $V$ 的前 $m$ 个元素放入结果集合 $R$。
对于结果集里的每个元素,计算特征值 $k_i = u_i^{(1/w_i)}$,其中 $u_i = rand(0, 1)$
对 $i = m+1, m+2, \dots, n$ 重复步骤 4 ~ 6
将结果集中最小的特征 $k$ 作为当前的阈值 $T$
对于元素 $v_i$,计算特征 $k_i = u_i^{(1/w_i)}$,其中 $u_i = rand(0, 1)$
如果 $k_i > T$ 则将 $R$ 中拥有最小 $k$ 值的