【问题标题】:Algorithm for generating random numbers from a discrete distribution?从离散分布生成随机数的算法?
【发布时间】:2012-04-21 07:16:02
【问题描述】:

设计一个快速算法来重复生成数字 离散分布:给定一个非负实数数组 a[] 总和为 1,目标是返回索引 i 的概率为 a[i]

我在一本在线算法书籍《Java 编程简介》第 4.2 章:排序和搜索 (http://introcs.cs.princeton.edu/java/42sort/) 中发现了这个问题。

提示说:

形成一个累加和的数组 s[],使得 s[i] 是 a[] 的前 i 个元素的和。现在,生成一个介于 0 和 1 之间的随机实数 r,并使用二分查找返回 s[i] ≤ s[i+1] 的索引 i。

有些我无法理解提示,因此无法找到解决方案..

【问题讨论】:

  • 到目前为止您尝试过哪些不起作用的方法?请发布您的代码并解释它如何无法按您的预期工作,这里的人将很乐意帮助您弄清楚如何修复它。不过,我们不只是为您做您的工作 - 您确实需要做一些工作来尝试自己先弄清楚。 :)
  • Data structure for loaded dice? 的可能重复项
  • 嗯,因为a[] 是非负数,所以s[i] <= s[i+1] 对所有i 都是正确的。提示似乎是错误的。我认为这意味着你必须返回第一个i,这样s[i] >= r

标签: algorithm math random


【解决方案1】:

这是一个实现“轮盘赌”技术的 Python 算法。没有图形很难解释。由 templatetypedef 链接的文章应该可以很好地解决这个问题。另外,请注意,此算法实际上并不需要对权重进行归一化(它们不需要总和为 1),但这仍然有效。

import random

trials = 50
selected_indices = []

# weights on each index
distrib = [0.1, 0.4, 0.2, 0.3]

index = random.randrange(0, len(distrib) - 1)
max_weight = max(distrib)
B = 0
# generate 'trials' random indices
for i in range (trials):

    # increase B by a factor which is
    # guaranteed to be much larger than our largest weight
    B = B + random.uniform(0, 2 * max_weight)

    # continue stepping through wheel until B lands 'within' a weight
    while(B > distrib[index]):
        B = B - distrib[index]
        index = (index + 1) % len(distrib)
    selected_indices.append(index)

print("Randomly selected indices from {0} trials".format(trials))
print(selected_indices)

【讨论】:

    【解决方案2】:

    根据粒度,您可以创建包含 100、1000 或 10000 个元素的索引。假设分布 (a,b,c,d) 且 p=(10%, 20%, 30%, 40%),我们创建一个 Map:

    val prob = Map ('a' -> 10, 'b' -> 20, 'c' -> 30, 'd' -> 40) 
    val index = (for (e <- prob;
      i <- (1 to e._2)) yield e._1 ).toList 
    
    index: List[Char] = List(a, a, a, a, a, a, a, a, a, a, 
    b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, 
    c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, 
    d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d)
    

    我们现在可以非常快速地选择所需概率的元素:

    val x = index (random.nextInt (100))
    

    x 现在是 40% d,10% a 等等。设置短,访问速度快。

    这些数字甚至不需要总和为100,但你必须计算一次范围,然后:

    val max = prob.map (_._2).sum 
    val x = index (random.nextInt (max))
    

    【讨论】:

      【解决方案3】:

      这是来自 wakkerbot/megahal 的 sn-p。这里的权重是(无符号的)整数,它们的总和在 node->childsum 中。为了获得最大速度,孩子按降序(或多或少)排序。 (权重预计呈幂律分布,只有少数高权重和许多较小的权重)

          /*
           *          Choose a symbol at random from this context.
           *          weighted by ->thevalue
           */
          credit = urnd( node->childsum );
          for(cidx=0; 1; cidx = (cidx+1) % node->branch) {
              symbol = node->children[cidx].ptr->symbol;
              if (credit < node->children[cidx].ptr->thevalue) break;
              /* 20120203 if (node->children[cidx].ptr->thevalue == 0) credit--; */
              credit -= node->children[cidx].ptr->thevalue;
          }
      done:
          // fprintf(stderr, "{+%u}", symbol );
          return symbol;
      

      【讨论】:

        【解决方案4】:

        有很多方法可以解决这个问题。 This article 描述了许多方法、它们的优势、劣势和运行时间。它以一个算法结束,该算法需要 O(n) 的预处理时间,然后在每个 O(1) 的时间内生成数字。

        您正在寻找的特定方法在“轮盘选择”下进行了描述。

        希望这会有所帮助!

        【讨论】:

          猜你喜欢
          • 2011-05-11
          • 2014-08-23
          • 1970-01-01
          • 1970-01-01
          • 2011-01-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多