代码改变世界

单次遍历,带权随机选取问题

2013-10-05 22:49  youxin  阅读(1298)  评论(0编辑  收藏  举报

在以前的链表单次遍历,随机选取问题中,我们采用水塘抽象方法解决了问题。问题结点带权呢?

问题描述:有一组数量未知的数据,每个元素有非负权重。要求只遍历一次,随机选取其中的一个元素,任何一个元素被选到的概率与其权重成正比。

设元素总数为n,当然在遍历结束前n是未知的。设第i(1 <= i <= n)个元素的权重为wi(> 0),则权重总和为w=\sum_{i=1}^{n}{w_i},也是在遍历结束时才知道的。根据题目要求,第i个元素被选取的概率应该等于p_i=\frac{w_i}{w}

 

 

虽然加了个权重,但解法依旧非常简单,在单次遍历,等概率随机选取问题中的RandomSelect函数上稍作修改就得到本问题的解法,依旧是O(n)时间,O(1)辅助空间:

from random import Random

def WeightedRandomSelect(rand=None):
  selection = None
  totalweight = 0.0
  if rand is None:
    rand = Random()
  while True:
    # Outputs the current selection and gets next item
    (item, weight) = yield selection
    totalweight += weight
    if rand.random() * totalweight < weight:
      selection = item

其中Python的random.random()返回[0, 1)之间的随机小数。

把rand.random()<weight/totalWeigth,则会有好理解一点。(同等概率选取类似,random(1,i)等于1就替换.i分之一,只不过不同概率用权重占比来表示了。)

 

 

 

算法很简单:对于任意的i(1 <= i <= n),按照如下方法给第i个元素分配一个键值key(其中ri是一个0到1之间等概率分布的随机数):

 

之后,如果要随机选取一个元素,就去key最大的那个;如果要选取m个元素,就取key最大的m个。

真不知道是怎么想出来的这样的方法,不过还是先来关注一下证明的过程。

算法的核心,计算每个元素的随机权重,python版:

key = rand.random() ** (1.0 / weight)
 
选择key最大的m个,就是结果

更多:

http://www.gocalf.com/blog/weighted-random-selection.html

http://www.gocalf.com/blog/weighted-random-selection-2.html

http://www.cnblogs.com/SuperBrothers/archive/2012/11/13/2768788.html