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