Arrays ires O(n)初始化时间、O(1)选择时间和O(n)内存

Arrays ires O(n)初始化时间、O(1)选择时间和O(n)内存,arrays,algorithm,random,Arrays,Algorithm,Random,以下是生成滚动加权n边模具的结果的算法(从这里可以从长度为n的数组中选择一个元素)。 作者假设您具有滚动公平骰子(floor(random()*n))和翻转有偏硬币(random()

以下是生成滚动加权n边模具的结果的算法(从这里可以从长度为n的数组中选择一个元素)。 作者假设您具有滚动公平骰子(
floor(random()*n)
)和翻转有偏硬币(
random()
)的功能

算法:Vose的别名方法 初始化:
  • 创建数组Alias和Prob,每个大小为n
  • 创建两个工作列表,小的和大的
  • 将每个概率乘以n
  • 对于每个标度概率pi:
  • 如果pi<1,则将i添加到Small
  • 否则(pi≥ 1) ,将i添加到大
  • 小的和大的都不是空的:(大的可以先清空)
  • 将第一个元件从小部件上拆下;叫它l
  • 将第一个元素从大屏幕上移除;叫它g
  • 设置Prob[l]=pl
  • 设置别名[l]=g
  • 设置pg:=(pg+pl)−1.(这是一个数值更稳定的选项。)

  • 如果pg这是我在生产中使用的PHP代码:

    /**
     * @return \App\Models\CdnServer
    */
    protected function selectWeightedServer(Collection $servers)
    {
        if ($servers->count() == 1) {
            return $servers->first();
        }
    
        $totalWeight = 0;
    
        foreach ($servers as $server) {
            $totalWeight += $server->getWeight();
        }
    
        // Select a random server using weighted choice
        $randWeight = mt_rand(1, $totalWeight);
        $accWeight = 0;
    
        foreach ($servers as $server) {
            $accWeight += $server->getWeight();
    
            if ($accWeight >= $randWeight) {
                return $server;
            }
        }
    }
    
    “命运之轮”O(n),仅用于小型阵列:

    function pickRandomWeighted(array, weights) {
        var sum = 0;
        for (var i=0; i<weights.length; i++) sum += weights[i];
        for (var i=0, pick=Math.random()*sum; i<weights.length; i++, pick-=weights[i])
            if (pick-weights[i]<0) return array[i];
    }
    
    函数选取随机加权(数组、权重){
    var总和=0;
    
    对于(var i=0;i另一种可能性是将从参数中提取的随机数与数组的每个元素相关联,该参数由该元素的权重给出。然后选择具有最低“排序数”的元素。在这种情况下,特定元素具有数组最低排序数的概率是成比例的到数组元素的权重

    这是O(n),不涉及任何重新排序或额外存储,可以在通过数组的单个过程中进行选择。权重必须大于零,但不必求和到任何特定值

    这还有一个好处,即如果存储每个数组元素的顺序号,则可以选择通过增加顺序号对数组进行排序,以获得数组的随机顺序,其中权重越高的元素提前到达的概率越高(我发现这在决定选择哪个DNS SRV记录、决定查询哪个机器时很有用)

    带替换的重复随机采样每次都需要重新通过数组;对于不带替换的随机选择,可以按照增加排序数的顺序对数组进行排序,并且可以按照该顺序读取k个元素

    参见(特别是关于此类变量集合的极小值分布的注释),以证明上述内容是正确的,同时也可参考生成此类变量的技术:如果T在[0,1]中具有均匀随机分布,则Z=-log(1-T)/w(其中w为分布参数;此处为相关元素的权重)具有指数分布

    即:

  • 对于数组中的每个元素i,计算zi=-log(T)/wi(或zi=-log(1-T)/wi),其中T来自[0,1]中的均匀分布,wi是第i个元素的权重
  • 选择zi最低的图元
  • 元素i将以概率wi/(w1+w2+…+wn)选择

    请参见下面的Python示例,它对10000次试验中的每一次进行一次权重数组传递

    import math, random
    
    random.seed()
    
    weights = [10, 20, 50, 20]
    nw = len(weights)
    results = [0 for i in range(nw)]
    
    n = 10000
    while n > 0: # do n trials
        smallest_i = 0
        smallest_z = -math.log(1-random.random())/weights[0]
        for i in range(1, nw):
            z = -math.log(1-random.random())/weights[i]
            if z < smallest_z:
                smallest_i = i
                smallest_z = z
    
        results[smallest_i] += 1 # accumulate our choices
    
        n -= 1
    
    for i in range(nw):
        print("{} -> {}".format(weights[i], results[i]))
    
    导入数学,随机
    random.seed()
    权重=[10,20,50,20]
    nw=长度(重量)
    结果=[0,适用于范围内的i(nw)]
    n=10000
    当n>0时:#不进行n次试验
    最小值i=0
    最小的_z=-math.log(1-random.random())/weights[0]
    对于范围(1,nw)内的i:
    z=-math.log(1-random.random())/weights[i]
    如果z<最小值_z:
    最小_i=i
    最小_z=z
    结果[最小值]+=1#累积我们的选择
    n-=1
    对于范围(nw)内的i:
    打印(“{}->{}”。格式(权重[i],结果[i]))
    

    编辑(历史):在发布这篇文章之后,我确信我不可能是第一个想到它的人,而另一次考虑到这个解决方案的搜索表明,事实确实如此

    • 在一篇文章中,他提出了这个算法(并指出,一定有人以前就想到过它)
    • 同时,另一个指向(),它描述了一个类似的方法
    • 我很确定,看着它,Efraimidis和Spirakis实际上是伪装的同一个指数分布算法,这被“[e]同样,该算法的一个数值上更稳定的公式是上面的指数分布算法。参考文献是;指数分布的相关属性在第1.3节中提到(其中提到类似于这一点在某些圈子中是“熟悉的事实”),但与Eframidis和Spirakis算法无关

    这是最明显的解决方案,但我不能真正将其用于我想要处理的数据量。这不起作用,因为我有机会,而不是这个领域。|尽管有人否决了这个答案,但它给了我一个可行的想法。限制非常简单,不会影响性能。@Mikulas假设你有discr概率和随机数平均分布在0和1之间,概率等于它们的权重。对于您的情况,有80%的概率随机数小于。8因此,将选择第一个元素,20%的概率大于。8在这种情况下,将选择第二个元素。这需要对数组进行排序,开始g被选中的几率最低,不是吗?这是我负担不起的计算。(注意,我没有保留以前选择的元素列表)不,如果你想在元素被选中后删除它,它不排序就可以工作,并且比二进制搜索更快。很抱歉,如果我有两个元素呢
    #each element is associated with its probability
    a = {1 => 0.25 ,2 => 0.5 ,3 => 0.2, 4 => 0.05}
    
    #at some point, convert to ccumulative probability
    acc = 0
    a.each { |e,w| a[e] = acc+=w }
    
    #to select an element, pick a random between 0 and 1 and find the first   
    #cummulative probability that's greater than the random number
    r = rand
    selected = a.find{ |e,w| w>r }
    
    p selected[0]
    
    h = {1 => 0.5, 2 => 0.3, 3 => 0.05, 4 => 0.05 }
    
    auxiliary_array = h.inject([]){|memo,(k,v)| memo += Array.new((100*v).to_i,k) }   
    
    ruby-1.9.3-p194 > auxiliary_array 
     => [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,                                 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4] 
    
    auxiliary_array.sample
    
    m = 10**h.values.collect{|e| e.to_s.split(".").last.size }.max
    
    require 'pickup'
    
    chances = {0=>80, 1=>20}
    picker = Pickup.new(chances)
    
    5.times.collect {
      picker.pick(5)
    }
    
    [[0, 0, 0, 0, 0], 
     [0, 0, 0, 0, 0], 
     [0, 0, 0, 1, 1], 
     [0, 0, 0, 0, 0], 
     [0, 0, 0, 0, 1]]
    
    /**
     * @return \App\Models\CdnServer
    */
    protected function selectWeightedServer(Collection $servers)
    {
        if ($servers->count() == 1) {
            return $servers->first();
        }
    
        $totalWeight = 0;
    
        foreach ($servers as $server) {
            $totalWeight += $server->getWeight();
        }
    
        // Select a random server using weighted choice
        $randWeight = mt_rand(1, $totalWeight);
        $accWeight = 0;
    
        foreach ($servers as $server) {
            $accWeight += $server->getWeight();
    
            if ($accWeight >= $randWeight) {
                return $server;
            }
        }
    }
    
    function pickRandomWeighted(array, weights) {
        var sum = 0;
        for (var i=0; i<weights.length; i++) sum += weights[i];
        for (var i=0, pick=Math.random()*sum; i<weights.length; i++, pick-=weights[i])
            if (pick-weights[i]<0) return array[i];
    }
    
    import math, random
    
    random.seed()
    
    weights = [10, 20, 50, 20]
    nw = len(weights)
    results = [0 for i in range(nw)]
    
    n = 10000
    while n > 0: # do n trials
        smallest_i = 0
        smallest_z = -math.log(1-random.random())/weights[0]
        for i in range(1, nw):
            z = -math.log(1-random.random())/weights[i]
            if z < smallest_z:
                smallest_i = i
                smallest_z = z
    
        results[smallest_i] += 1 # accumulate our choices
    
        n -= 1
    
    for i in range(nw):
        print("{} -> {}".format(weights[i], results[i]))