问题
给定一个数据流,数据流长度 N N N很大,且 N N N直到处理完所有数据之前都不可知,请问如何在只遍历一遍数据(O(N))的情况下,能够等概率随机选取出 k k k个不重复的数据。
解决
这个主要考察蓄水池算法,具体就是:保存一个 k k k大小的窗口,然后依次接收数据流,对于数据流第 i i i个元素( i > k i>k i>k),以 k i \frac{k}{i} ik 的概率替换窗口中的某个元素,最终得到的 k k k个元素就是均匀采样得到的,每个元素被采到的概率都是 k N \frac{k}{N} Nk。
python实现:
import random
def reservoir_sampling(window, k, i, data):
"""
每次得到数据流中的一个元素后怎么更新窗口
:param window: 窗口
:param k: 需要采样k个元素
:param i: 当前数据流元素的索引,从0开始
:param data: 当前得到的数据流元素
:return: 更新后的window
"""
if len(window) < k:
window.append(data)
else:
# 从[0, i]中随机得到一个整数
replace = random.randint(0, i)
if replace < k:
window[replace] = data
return window
使用数学归纳法给出该算法合理性的简单证明。
- 假设 i = 1 i=1 i=1,每个元素被选中的概率为1;
- 设前
i
i
i个元素被选中的概率为
k
i
\frac{k}{i}
ik。对于第
i
+
1
i+1
i+1个元素,我们以
k
i
+
1
\frac{k}{i+1}
i+1k的概率替换窗口中的元素,也就是说对于这个元素,被选中的概率是
k
i
+
1
\frac{k}{i+1}
i+1k。对于窗口中原有的
k
k
k个元素,其被替换掉的概率为
k
i
+
1
∗
1
k
\frac{k}{i+1}*\frac{1}{k}
i+1k∗k1,那么除了最近得到的元素,其余元素被采样得到的概率为:
k i ( 1 − k i + 1 1 k ) = k i + 1 \frac{k}{i}(1-\frac{k}{i+1}\frac{1}{k})=\frac{k}{i+1} ik(1−i+1kk1)=i+1k
所以遍历数据流之后,得到的 k k k个元素是以概率 k N \frac{k}{N} Nk均匀采样得到的。
除此之外也有分布式的蓄水池采样,具体就是分 P P P个数据流: N 1 , ⋯ , N P , ∑ p = 1 P N p = N N_1,\cdots,N_P,\sum_{p=1}^{P}N_p=N N1,⋯,NP,∑p=1PNp=N,假设对于任意数据流都有 N p > = k N_p>=k Np>=k,分别进行蓄水池采样,得到 P P P个大小为 k k k的蓄水池。
然后按照数据流的大小进行选择,即对于数据流 N p N_p Np,其蓄水池被选中的概率为 N p N \frac{N_p}{N} NNp,并从中随机移出一个元素,重复 k k k次即可。这个过程可以分解为两两一组合并,类似归并,这样又可以并行了。
每个元素每次被采样到的概率为 k N p N p N 1 k = 1 N \frac{k}{N_p}\frac{N_p}{N}\frac{1}{k}=\frac{1}{N} NpkNNpk1=N1(这个 k k k可以看做当前蓄水池的大小),重复 k k k次,任意元素被采样到的概率为 k N \frac{k}{N} Nk。