蓄水池问题

怎样随机从N个元素中选择一个元素,你依次遍历每个元素,但不知道N多大。

将N个元素用[1、2、...、N]编号。如果在知道N的大小,我们可以从[1、N]中随机选择一个数作为选择对象。

但是现在不知道N的大小,要使每一个元素被取的概率相等(随机)。这个概念叫蓄水池抽样。

Solution:以1/i的概率取第i个元素。

证明:数学归纳法。

当i=1时:第1个元素以1/1=1的概率被取,符合条件。

设i=k时符合条件,即前k个元素都以1/k的概率被取。

则i=k+1时:对于第k+1个元素,被取概率为1/(k+1),符合条件。

对于前k个元素,每个元素被取的概率=被取并且没被第k+1个元素替换的概率=(1/k)*(1−1/(k+1))=1/(k+1)符合条件。

综上所述:得证。

问题扩展:给你一个长度为N的链表。N很大,但你不知道N有多大。你的任务是从这N个元素中随机取出k个元素。你只能遍历这个链表一次。你的算法必须保证取出的元素恰好有k个,且它们是完全随机的(出现概率均等)?

同类:有l个奖品,一直有人来抽奖,一个奖品只能给一个人,最后才公布获奖,请设计一个算法使得每个人中奖的概率一样?

这次与上面唯一的不同是:总共需要取k个元素。仿照即可得出解决方案。

Solution:以1的概率取前k个元素,从i=k+1开始,以k/i的概率取第i个元素,若被取,以均等的概率替换先前被取的k个元素。

证明:同样数学归纳法。当i=k+1时:第k+1个元素以k/k+1概率被取,前k个元素被取的概率=1 - 被第k+1个元素替换的概率=1−k/(k+1)*1/k=k/(k+1) 符合条件。

设i=p时符合条件,即前p个元素都以k/p的概率被取。

则i=p+1时:对第p+1个元素,被取概率为k/(p+1)符合条件。

对于前p个元素,每个元素被取的概率=被取并且没有被第p+1个元素替换的概率=

k/p*((1−k/(p+1))+k/(p+1)*(1−1/k))=k/p+1同样符合条件。

综上所述:得证。

还有一种方法:给每个元素随机生成一个固定区间(如[0,1])的权重。用一个大小为k的堆来选取权重较大的k个元素。仿照也可解决最开始的取1个的问题。

伪代码:《The Art of Computer Programming》

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

[海量数据随机抽样问题(蓄水池问题)]

[大数据工程师必备之蓄水池抽样算法]*

Code

!/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()

results matching ""

    No results matching ""