如何有效地进行一些随机试验?

时间:2018-05-04 14:01:33

标签: c# algorithm formula probability

假设某个事件的概率 P成功。 (0 < P < 1 ) 我必须进行N测试,看看是否会发生这种情况,我想要成功的总数:

我可以去

int countSuccesses = 0;
while(N-- > 0)
{
   if(Random.NextDouble()<P) countSuccesses++; // NextDouble is from 0.0 to 1.0
}

但有没有更有效的方法呢?我想要一个公式,所以我可以使用一个draw 随机数来确定成功的总数。 (编辑仅使用一次抽奖的想法是低于O(n))

我希望能够调用方法

GetSuccesses( n, P)

它是O(1)

更新
我会试着去 MathNet.Numerics.Distributions.Binomial.Sample(P, n) 即使它可能只使用了一个随机数,但我猜它会比O(n)更快,即使它不是O(1)。我会对此进行基准测试。非常感谢David和Rici。

更新
上面的二项式样本是O(n)所以它对我没有帮助。但是由于弗雷德的评论,我刚刚切换到了 MathNet.Numerics.Distributions.Normal.Sample(mean, stddev) 其中
mean = n * P
stddev = Math.Sqrt(n * P * (1 - P)); 现在是O(1)

4 个答案:

答案 0 :(得分:2)

对于小N,每个@rici你可以使用二项分布的CDF或PMF,并简单地将随机输入与0,1,2..N成功的概率进行比较。

类似的东西:

  static void Main(string[] args)
    {

        var trials = 10;
        var trialProbability = 0.25;
        for (double p = 0;  p <= 1; p += 0.01)
        {
            var i = GetSuccesses(trials, trialProbability, p);
            Console.WriteLine($"{i} Successes out of {trials} with P={trialProbability} at {p}");
        }
        Console.ReadKey();

    }
    static int GetSuccesses(int N, double P, double rand)
    {
        for (int i = 0; i <= N; i++)
        {
            var p_of_i_successes = MathNet.Numerics.Distributions.Binomial.PMF(P, N, i);
            if (p_of_i_successes >= rand)
                return i;

            rand -= p_of_i_successes;

        }
        return N;


    }

答案 1 :(得分:1)

我不打算在这里写公式,因为它已经在wiki中了,而且我不太清楚这些事情的格式。

每个结果的概率可以通过Bernulli公式确定 https://en.wikipedia.org/wiki/Bernoulli_trial

你需要做的是计算二项式系数,然后概率计算变得非常容易 - 将二项式系数乘以p和q的适当幂。填写数组P [0..n],其中包含每个结果的概率 - 完全成功的数量。

设置后从0到n并计算概率的滚动总和。 检查下限/上限与随机值,一旦在当前时间间隔内,返回结果。

因此,决定部分将是这样的:

sum=0;
for (int i = 0; i <= n; i++)
  if (sum-eps < R && sum+P[i]+eps > R)
    return i;
  else
    sum+=P[i];

这里eps是小浮点值以克服浮点舍入问题,R是保存的随机值,P是我之前提到的概率数组。

不幸的是,这种方法对于大N(20或100 +)不实用:

  • 你会对舍入错误产生很大的影响

  • 随机数生成器可能不具有足够的确定性,无法通过适当的概率分布来涵盖所有可能的结果

答案 2 :(得分:0)

根据你如何表达问题,这是不可能做到的。

你基本上是在询问如何确保单个硬币翻转(即一个随机化结果)正好是50%的头部和50%的尾部,这是不可能的。

即使你要使用两个随机数,你期望一个头和一个尾巴;在所有情况下,这个测试都会失败50%(因为你可能得到两个头或两个尾巴)。

Probablility建立在the law of large numbers之上。这明确指出,不能期望小样本准确反映预期结果。

  

LLN很重要,因为它可以保证一些随机事件平均值的长期稳定结果。例如,虽然赌场可能会在轮盘赌的单轮旋转中亏损,但其盈利将在大量旋转中趋于可预测的百分比。任何玩家的连胜最终都会被游戏的参数所克服。重要的是要记住,当考虑大量观察时,法律仅适用(如名称所示)。 没有原则,少数观察会与预期值重合,或者一个值的连续性会立即被其他值“平衡”(参见赌徒的谬误)。

当我问这个评论时;你回答说:

  

@Flater不,我正在进行N次实际抽签,但只有一个随机数。

但这没有意义。如果你只使用一个随机值,并继续使用相同的值,那么每次抽取显然会给你完全相同的结果(相同的数字)。

我能以一种并非不可能的方式解释你的问题,那就是你错误地将单个random seed称为单个随机数。

  

随机种子(或种子状态,或仅种子)是用于初始化伪随机数生成器的数字(或向量)。

     

对于要在伪随机数生成器中使用的种子,它不需要是随机的。由于数字生成算法的性质,只要忽略原始种子,算法生成的其余值将以伪随机方式遵循概率分布。

但是,您明确提到的期望似乎反驳了这一假设。你想做类似的事情:

GetSuccesses( n, P, Random.NextDouble())

并且你也希望得到一个O(1)操作,这个操作面对大数定律。

如果你真的在谈论有一个随机种子;那么你的期望是不正确的。

  • 如果进行N次绘制,则操作的复杂度仍为O(N)。无论你是否在每次抽奖后随机化种子都是无关紧要的,它总是O(N)
  • GetSuccesses( n, P, Random.NextDouble())将为您提供一次抽奖,而非一粒种子。无论使用何种术语,您对代码的期望与使用相同种子进行多次绘制无关。

目前的问题是措辞;你想要的是不可能的。一些评论者的反复意见澄清尚未产生更清晰的图景。

作为旁注,我发现你回答每个评论除了直接询问你是在谈论种子而不是数字(现在两次)时,我觉得很奇怪。

答案 3 :(得分:0)

@David and @rici向我指出了 MathNet.Numerics.Distributions.Binomial.Sample(P, n)
 一个基准测试告诉我它也是O(n)并与我原来的

相提并论
int countSuccesses = 0;
while(N-- > 0)
{
   if(Random.NextDouble()<P) countSuccesses++; // NextDouble is from 0.0 to 1.0
}

但感谢Fred做的评论:

  

您可以将该随机数转换为具有均值N * P的高斯样本,其与您的初始函数具有相同的分布

我刚转到了 MathNet.Numerics.Distributions.Normal.Sample(mean, stddev) 其中
mean = n * P
stddev = Math.Sqrt(n * P * (1 - P)); 现在是O(1)

我想要的功能是:

    private int GetSuccesses(double p, int n)
    {
        double mean = n * p;
        double stddev = Math.Sqrt(n * p * (1 - p));
        double hits = MathNet.Numerics.Distributions.Normal.Sample(Random, mean, stddev);
        return (int)Math.Round(hits, 0);
    }

保罗指出, 这是一个近似值,但我很乐意接受。