题目描述:
给出一个数据流,这个数据流的长度很大或者未知,并且对该数据流中数据只能访问一次。请写出一个随机选择算法,使得数据流中所有数据被选中的概率相等。
或者也可以这么说:
要求从N个元素中随机的抽取k个元素,其中N的大小未知。
解题思路:
用到的方法为蓄水池抽样算法(reservoid sampling)。具体的思路是:先初始化一个集合,集合中有k个元素,将此集合作为蓄水池。然后从第k+1个元素开始遍历,并且按一定的概率替换掉蓄水池里面的元素。
伪代码:
Init : a reservoir with the size: k
for i= k+1 to N
M=random(1, i);
if( M < k)
SWAP the Mth value and ith value
end for
具体描述如下:先将前k个数取出来放入结果集中,然后从第k+1个数开始遍历。假设遍历到第i个数,以 k/i 的概率替换掉蓄水池中的某个元素即可。
!/usr/bin/env python
#coding:utf-8
import random
import collections
#用蓄水池算法模拟从10个数中随机抽取一个数
def reservoir():
raw_list = [0,1,2,3,4,5,6,7,8,9]
ret_num = raw_list[0] #蓄水池初始化。因为只需要抽取一个,所以给一个变量即可
for i in range(1,10): #从第k+1个元素开始遍历
m = random.randint(1,i+1) #因为列表下标从0开始,所以随机数上限为i+1而不是i
if m <= 1:
ret_num = raw_list[i] #蓄水池里的元素替换
return ret_num
#抽取十万次,看看最后的结果
def run():
dic = collections.defaultdict(int)
for i in range(100000):
ret_num = reservoir()
dic[ret_num] += 1
for k,v in dic.items():
print(k,":",v)
run()
从结果可以看出,每个数被抽到的次数基本时接近的。
0 : 9926
1 : 10024
2 : 10056
3 : 10043
4 : 10004
5 : 9826
6 : 10083
7 : 10036
8 : 9843
9 : 10159