如何 return 元素的索引,元素值除以数组总和的概率

How to return index of an element with probability of the element's value divided by sum of array

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

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

return 0 概率为 1/10;

return 1 概率为 4/10;

return 2 概率为 2/10;

return 3 概率为 3/10;

问题2:数组中出现重复项如何处理?

我一直认为二分查找可以很好地找到数组中的元素,但是我还没有想出如何将它与概率联系起来。

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

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

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

编辑(归功于 JuanLopez): 但是,如果您要回答多个查询,则可以使用 prefix sum 中的预计算并将其与二进制搜索相结合以找到 sum x[=15 的确切位置 k =]i=0ai 将小于 k 且 x 最大。请注意,在进行前缀和预计算之后,您可以计算常量中的 sum xi=0ai时间。

Given an array and a value k, write a function to return index of element that equals to k with the probability of k/sum(input array)

您可以将问题简化为 [1, sum] 的均匀采样。这个想法是使用初始列表的累积列表 cum_distr 并在 [1,sum] 中统一采样一个数字 r 并找到最高的 i 这样的 r<=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 是非递减的。

How to deal with it if there are duplicates in the array?

没关系

您可以根据输入创建一个累积数组,其中 B[i] = A[0] + A[1] + ... + A[i]。在1sum(A)之间生成一个随机intx,然后二分查找B第一个不小于x.

的元素

这是 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})

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

[我用这个技巧从 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;
}