【问题标题】:How to return index of an element with probability of the element's value divided by sum of array如何以元素值除以数组总和的概率返回元素的索引
【发布时间】:2015-12-28 05:45:43
【问题描述】:

给定一个数组和一个值 k,编写一个函数以返回等于 k ​​的元素的索引,概率为 k/sum(输入数组)。假设输入数组中没有重复的数字。

例如,如果输入数组是 1,4,2,3。该函数应具有以下行为:

以 1/10 的概率返回 0;

以 4/10 的概率返回 1;

以 2/10 的概率返回 2;

以 3/10 的概率返回 3;

问题2:如果数组有重复怎么处理?

我在想二进制搜索可以很好地在数组中找到一个元素,但是我还没有弄清楚如何将它与概率联系起来。

已编辑: 正如建议的那样,this question 与我的问题类似。然而,它的解决方案并不是我所期望的。我一直在寻找嵌入二分搜索的解决方案,这可能会降低时间复杂度。

A good solution 关于给定一个键,如何使用二分搜索在排序数组中找到大于键的第一个元素。

【问题讨论】:

标签: arrays algorithm probability binary-search


【解决方案1】:

这看起来像简单的采样器(实际上是) ,但检查元素的顺序有一种微妙之处。 通过将最大的权重放在前面,循环通常只需几次迭代即可完成。因此,如果分布非常偏斜,则此方法平均而言可能更快。

[我使用这个技巧从 Wakkerbot 中马尔可夫节点中使用的随机向量中采样]

#include <stdio.h>
#include <stdlib.h>

struct samp {
    int ret;
    unsigned weight;
    } array[4] = {{ 1,4}, { 3,3}, {2,2}, { 0,1} };

unsigned sumweight = 10;

     /* this is a *terrible* way to obtain a uniform random value */
#define urand(n) (random() % (n))

int sample(void)
{
unsigned idx, val;

val = urand(sumweight);

for( idx=0; idx < 4; idx++ ) {
    if (val < array[idx].weight) return array[idx].ret;
    val -= array[idx].weight;
    }
return -1;
}

int main(void)
{
int ret;
unsigned loop;

for (loop = 0; loop < 20; loop++) {
    ret = sample();
    printf("%u: %d\n" , loop, ret);
    }
return 0;
}

【讨论】:

    【解决方案2】:

    对所有元素求和(表示和 S),然后生成一个从 1 到 S 的随机数 r。然后遍历所有数字 ai。如果 ai 不小于 r,则返回 ai。否则从 r 中减去 ai。继续直到返回一个值。如果您有一个查询,您将无法改进此解决方案。

    编辑(感谢胡安洛佩兹): 但是,如果您要回答多个查询,则可以使用 prefix sum 中的预计算并将其与二分搜索结合起来以找到 sum xi=0 的确切位置 k sub>ai 将小于 k 并且 x 是最大值。请注意,在进行前缀总和预计算之后,您可以在恒定时间内计算 sum xi=0ai

    【讨论】:

    • 二进制搜索在这里会有所帮助。您可以对 O(log n) 中的累积数组执行二进制搜索,而不是遍历 O(n) 中的所有数字。
    • @JuanLopes 好点。我已编辑我的答案以包含建议的改进。
    • 感谢您的解决方案。但是,在最坏的情况下,减法将永远持续下去。前缀总和可能更好,尤其是当数组很大时。
    【解决方案3】:

    您可以从输入中创建一个累积数组,其中B[i] = A[0] + A[1] + ... + A[i]。在1sum(A)之间生成一个随机整数x,然后对不小于x的第一个元素进行二分搜索B。

    这是一个 Python 示例(使用 Python 的 bisect 模块,这本质上是一种二分搜索)。

    import random, bisect, collections
    
    def make_random(A):
        s = sum(A)
        B = list(A)
        for i in xrange(1, len(B)):
            B[i] += B[i-1]
        def fn():
            r = random.randint(1, s)
            return bisect.bisect_left(B, r)
        return fn
    
    rnd = make_random([1,4,2,3])
    
    c = collections.Counter()
    for i in xrange(10000):
        c[rnd()]+=1
    
    print c
    

    结果将如下所示:

    Counter({1: 3960, 3: 3036, 2: 1992, 0: 1012})
    

    【讨论】:

      【解决方案4】:

      给定一个数组和一个值 k,编写一个函数来返回 等于 k ​​且概率为 k/sum(输入数组)的元素

      您可以从[1, sum] 将问题简化为统一抽样。想法是使用你初始列表的累积列表cum_distr,并在[1,sum]中统一采样一个数字r,并找到最高的i这样r&lt;=cum_distr[i]

      import random
      
      
      def get_cum_distr(distr):
          cum_distr = []
          sum = 0
          for i in range(len(distr)):
              sum += distr[i]
              cum_distr.append(sum)
          return cum_distr
      
      
      def sampler(cum_distr):
          r = random.randint(1, cum_distr[-1])
          i = 0
          while r > cum_distr[i]:
              i += 1
          return i
      
      
      distr = [1, 4, 2, 3]
      cum_distr = get_cum_distr(distr)
      #test sampler
      sample_size = 100000
      samples = []
      count = dict()
      for _ in range(sample_size):
          r = sampler(cum_distr)
          if r in count:
              count[r] += 1
          else:
              count[r] = 1
      #{0: 9996, 1: 40115, 2: 19934, 3: 29955}
      

      请注意,如果搜索索引的开销很大,您可以使用二分搜索代替,因为cum_distr 是非递减的。

      如果数组有重复怎么处理?

      没关系。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-09-08
        • 2016-07-22
        • 2012-07-20
        • 2019-04-21
        • 1970-01-01
        • 2014-07-30
        • 2021-08-07
        • 1970-01-01
        相关资源
        最近更新 更多