Answers:
有一个简单的算法可以随机选择一个项目,其中项目具有各自的权重:
1)计算所有权重之和
2)选择一个大于或等于0且小于权重总和的随机数
3)一次检查一个项目,从您的随机数中减去它们的权重,直到获得随机数小于该项目权重的项目
伪代码说明了这一点:
int sum_of_weight = 0;
for(int i=0; i<num_choices; i++) {
sum_of_weight += choice_weight[i];
}
int rnd = random(sum_of_weight);
for(int i=0; i<num_choices; i++) {
if(rnd < choice_weight[i])
return i;
rnd -= choice_weight[i];
}
assert(!"should never get here");
这应该很容易适应您的增压容器等。
如果您的权重很少改变,但是您经常随机选择一个,并且只要您的容器存储指向对象的指针或长度超过几十个项目(基本上,您就必须剖析以了解这是有帮助还是有障碍) ,那么有一个优化:
通过将累积重量总和存储在每个项目中,您可以使用二进制搜索来选择与拾取重量相对应的项目。
如果您不知道列表中的项目数,那么有一个非常简洁的算法称为储层采样,可以对其进行加权。
A Monte Carlo method called Russian roulette is used to choose one of these actions
在进行谷歌搜索时会出现在存储桶中。“俄罗斯轮盘赌算法”。您可能会争辩说,所有这些人的名字都错了。
更新了对旧问题的答案。您只需使用std :: lib就可以在C ++ 11中轻松地做到这一点:
#include <iostream>
#include <random>
#include <iterator>
#include <ctime>
#include <type_traits>
#include <cassert>
int main()
{
// Set up distribution
double interval[] = {1, 2, 3, 4};
double weights[] = { .90, .56, .04};
std::piecewise_constant_distribution<> dist(std::begin(interval),
std::end(interval),
std::begin(weights));
// Choose generator
std::mt19937 gen(std::time(0)); // seed as wanted
// Demonstrate with N randomly generated numbers
const unsigned N = 1000000;
// Collect number of times each random number is generated
double avg[std::extent<decltype(weights)>::value] = {0};
for (unsigned i = 0; i < N; ++i)
{
// Generate random number using gen, distributed according to dist
unsigned r = static_cast<unsigned>(dist(gen));
// Sanity check
assert(interval[0] <= r && r <= *(std::end(interval)-2));
// Save r for statistical test of distribution
avg[r - 1]++;
}
// Compute averages for distribution
for (double* i = std::begin(avg); i < std::end(avg); ++i)
*i /= N;
// Display distribution
for (unsigned i = 1; i <= std::extent<decltype(avg)>::value; ++i)
std::cout << "avg[" << i << "] = " << avg[i-1] << '\n';
}
我的系统上的输出:
avg[1] = 0.600115
avg[2] = 0.373341
avg[3] = 0.026544
请注意,上面的大多数代码仅用于显示和分析输出。实际的生成只是几行代码。输出表明已获得请求的“概率”。您必须将请求的输出除以1.5,因为这就是请求的总和。
std::discrete_distribution
不是std::piecewise_constant_distribution
会是更好的。
如果您的权重变化比绘制的速度慢,则C ++ 11 discrete_distribution
将是最简单的:
#include <random>
#include <vector>
std::vector<double> weights{90,56,4};
std::discrete_distribution<int> dist(std::begin(weights), std::end(weights));
std::mt19937 gen;
gen.seed(time(0));//if you want different results from different runs
int N = 100000;
std::vector<int> samples(N);
for(auto & i: samples)
i = dist(gen);
//do something with your samples...
但是请注意,c ++ 11 discrete_distribution
在初始化时会计算所有累积和。通常,您需要这样做是因为它可以将采样时间缩短为O(N)成本的一倍。但是对于快速变化的发行版,将导致沉重的计算(和内存)成本。例如,如果权重代表有多少个项目,并且每次绘制一个项目,都将其删除,则可能需要自定义算法。
Will的答案https://stackoverflow.com/a/1761646/837451避免了这种开销,但是从中提取比C ++ 11慢,因为它不能使用二进制搜索。
要看到它能做到这一点,您可以看到相关的行(/usr/include/c++/5/bits/random.tcc
在我的Ubuntu 16.04 + GCC 5.3安装上):
template<typename _IntType>
void
discrete_distribution<_IntType>::param_type::
_M_initialize()
{
if (_M_prob.size() < 2)
{
_M_prob.clear();
return;
}
const double __sum = std::accumulate(_M_prob.begin(),
_M_prob.end(), 0.0);
// Now normalize the probabilites.
__detail::__normalize(_M_prob.begin(), _M_prob.end(), _M_prob.begin(),
__sum);
// Accumulate partial sums.
_M_cp.reserve(_M_prob.size());
std::partial_sum(_M_prob.begin(), _M_prob.end(),
std::back_inserter(_M_cp));
// Make sure the last cumulative probability is one.
_M_cp[_M_cp.size() - 1] = 1.0;
}
当我需要对数字进行加权时,我会使用一个随机数字作为加权。
例如:我需要生成具有以下权重的1到3的随机数:
然后我用:
weight = rand() % 10;
switch( weight ) {
case 0:
randomNumber = 1;
break;
case 1:
case 2:
case 3:
randomNumber = 2;
break;
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
randomNumber = 3;
break;
}
这样一来,它随机地有10%的概率是1、30%的概率是2和60%的概率是3。
您可以根据需要使用它。
希望我能帮助您,祝您好运!
建立一个可以选择的所有物品的袋子(或std :: vector)。
确保每个项目的数量与您的权重成正比。
例:
因此,要有一个包含100个物品的袋子,其中包括60个1、35 2个和5 3个。
现在对袋子进行随机排序(std :: random_shuffle)
依次从袋子中拾取元素,直到袋子变空为止。
清空后,重新随机包装袋,然后重新开始。
1,2,2
产生1 1/3的时间和2 2/3的时间。随机化数组,选择第一个,假设为2,现在选择的下一个元素遵循1 1/2时间和2 1/2时间的分布。精明吗?
在[0,1)上选择一个随机数,它应该是增强RNG的默认operator()。选择具有累积概率密度函数> =该数字的项目:
template <class It,class P>
It choose_p(It begin,It end,P const& p)
{
if (begin==end) return end;
double sum=0.;
for (It i=begin;i!=end;++i)
sum+=p(*i);
double choice=sum*random01();
for (It i=begin;;) {
choice -= p(*i);
It r=i;
++i;
if (choice<0 || i==end) return r;
}
return begin; //unreachable
}
其中random01()返回> = 0和<1的双精度数。请注意,以上并不要求总和为1;它为您规范化它们。
p只是一个为集合[begin,end)中的一个项目分配概率的函数。如果您只有一系列概率,则可以忽略它(或使用一个标识)。