【问题标题】:An algorithm to calculate probability of a sum of the results happening一种计算结果总和发生概率的算法
【发布时间】:2011-09-17 15:47:00
【问题描述】:

我正在谈论使用的算法将允许您向它呈现 x 个项目,每个项目的范围为 a 到 b,结果为 y。我想要一个算法,当呈现出所描述的值时,它会输出它发生的可能性。

例如,对于两个骰子。因为我已经认识他们了(因为可能的结果太低了)。它会告诉你每一种可能性。

设置将类似于。 x=2 a=1 b=6。如果您想知道它产生 2 的机会。那么它只会吐出 1/36(或它的浮点值)。如果你把 7 作为总和,它会告诉你 6。

所以我的问题是,有没有一种简单的方法可以通过已经编写好的算法来实现这样的事情。或者是否必须遍历每个项目的每一次迭代才能获得每个值的组合总数。

确切的公式还可以为您提供从 1 到 12 的每个值的组合。

因此,它会为您提供一个分布数组,其中包含每个索引处的每个组合。如果是0-12。那么 0 有 0,1 有 0,2 有 1。

我觉得这是其他人已经遇到并想要解决的问题类型,并且算法已经完成。如果有人有一种简单的方法来做到这一点,而不仅仅是循环遍历所有可能的值,那就太棒了。

我不知道我为什么要解决这个问题,但由于某种原因,我今天有一种想要解决它的感觉。因为我一直在谷歌搜索,并使用 wolfram alpha,并亲自尝试过。我认为是时候认输并询问社区了。

我希望算法在 c 中,或者 PHP 中(尽管我不希望它不是,因为它慢得多)。使用 c 的原因仅仅是因为我想要原始速度,并且我不想处理类或对象。

伪代码或 C 是​​显示您的算法的最佳方式。

编辑:

另外,如果我因为数学问题而冒犯了他名字中带有“b”的人,我很抱歉。因为我没有冒犯的意思,但我只想声明 不明白。但答案可能会一直留在那里,因为我确信有人可能会提出这个问题并了解其背后的数学原理。

我也无法决定我想用哪种方式编写代码。我想我会尝试同时使用这两种方法,然后决定我更喜欢在我的小图书馆中看到/使用哪一种。

我忘了说的最后一件事是,微积分大约是五年前的四。我对概率、统计和随机性的理解来自我自己通过查看代码/阅读维基百科/阅读书籍的学习。

如果有人好奇是什么引发了这个问题。我有一本书,我正在推迟阅读,名为The Drunkards Walk,当我说 XKCD 904 时,我决定是时候开始阅读它了。然后两天前,当我要睡觉的时候……我思考了如何通过一个简单的算法来解决这个问题,并且能够想到一个。

我对代码的编码理解来自于修补其他程序,看看我破坏某些东西时会发生什么,然后在查看内置函数的文档时尝试我自己的东西。我确实通过阅读维基百科(尽可能多的)理解大 O 符号,伪代码是因为它与 python 非常相似。我自己,不能写伪代码(或者说大学老师)。我不断收到诸如“让它不像真正的代码让它更像伪代码”这样的笔记。那件事没有改变。

编辑 2:以防任何搜索此问题的人都很快想要代码。我已经把它包括在下面了。它是在 LGPLv3 下获得许可的,因为我确信存在此代码的闭源等效项。

它应该是相当可移植的,因为它完全是用 c 编写的。如果想将它变成用 c 编写的各种语言中的任何一种的扩展,那么这样做应该花费很少的精力。我选择“标记”第一个链接到“Ask Dr. Math”作为答案,因为它是我用于这个问题的实现。

第一个文件名为“sum_probability.c”

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>

/*!
*    file_name: sum_probability.c
*    
*    Set of functions to calculate the probabilty of n number of items adding up to s
*    with sides x. The question that this program relates to can be found at the url of
*    http://stackoverflow.com/questions/6394120/
*    
*     Copyright 2011-2019, Macarthur Inbody
*    
*   This program is free software: you can redistribute it and/or modify
*   it under the terms of the Lesser GNU General Public License as published by
*   the Free Software Foundation, either version 3 of the License, or
*   (at your option) any later version.
*
*   This program is distributed in the hope that it will be useful,
*   but WITHOUT ANY WARRANTY; without even the implied warranty of
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*   GNU General Public License for more details.
*
*   You should have received a copy of the Lesser GNU General Public License
*   along with this program.  If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*     
*   2011-06-20 06:03:57 PM -0400
*    
*   These functions work by any input that is provided. For a function demonstrating it.
*   Please look at the second source file at the post of the question on stack overflow.
*   It also includes an answer for implenting it using recursion if that is your favored
*   way of doing it. I personally do not feel comfortable working with recursion so that is
*   why I went with the implementation that I have included.
*
*/

/*
* The following functions implement falling factorials so that we can
* do binomial coefficients more quickly.
* Via the following formula.
*
*   K
*  PROD    (n-(k-i))/i
*   i=1;
*
*/

//unsigned int return
unsigned int m_product_c( int k,  int n){
    int i=1;
    float result=1;
    for(i=1;i<=k;++i){
        result=((n-(k-i))/i)*result;
    }
    return result;
}

//float return
float m_product_cf(float n, float k){
    int i=1;
    float result=1;
    for(i=1;i<=k;++i){
        result=((n-(k-i))/i)*result;
    }
    return result;
}


/*
* The following functions calculates the probability of n items with x sides
* that add up to a value of s. The formula for this is included below.
*
* The formula comes from. http://mathforum.org/library/drmath/view/52207.html
*
*s=sum
*n=number of items
*x=sides
*(s-n)/x
* SUM  (-1)^k * C(n,k) * C(s-x*k-1,n-1)
* k=0
*
*/

float chance_calc_single(float min, float max, float amount, float desired_result){
    float range=(max-min)+1;
    float series=ceil((desired_result-amount)/range);
    float i;
    --amount;
    float chances=0.0;
    for(i=0;i<=series;++i){
        chances=pow((-1),i)*m_product_cf(amount,i)*m_product_cf(desired_result-(range*i)-1,amount)+chances;
    }
    return chances;
}

这是我在上一个文件中所说的显示实现的文件。

#include "sum_probability.c"

/*
* 
* file_name:test.c
*
* Function showing off the algorithms working. User provides input via a cli
* And it will give you the final result.
*
*/
int main(void){
        int amount,min,max,desired_results;
        printf("%s","Please enter the amount of items.\n");
        scanf("%i",&amount);
        printf("%s","Please enter the minimum value allowed.\n");
        scanf("%i",&min);
        printf("%s","Please enter the maximum value allowed.\n");
        scanf("%i",&max);
        printf("%s","Please enter the value you wish to have them add up to. \n");
        scanf("%i",&desired_results);
        printf("The total chances for %i is %f.\n", desired_results, chance_calc_single(min, max, amount, desired_results));
}

【问题讨论】:

  • 绝对不是。我今天醒来,脑子里有这个问题。但我想让它高效。就像前几天,我在 PHP 中完全重写了 prng,以便它使用更好的熵源并生成分布更均匀的数字。你有没有因为想以某种方式找到答案而发痒?请告诉我,你还没有达到不再随机寻求新知识的地步。
  • 哈哈,你本来可以拒绝的。只是有些学生试图寻找捷径。我的坏:)
  • 如果你对这类问题感兴趣,你可能想看看 prolog。
  • 我从来都不是那种用非常简短的语句陈述事情的人。我通常有说太多而不是说不够的问题。我可以想象人们会在这里尝试做类似的事情。
  • @Micheal 我会调查的。这只是我对事物的随机问题之一。它每天都在变化。有些日子,我什至不从事我的个人项目。几天前,它是关于编译器如何处理大于其限制的数字数据类型。我发现这只是通过模数完成的“整数溢出”。大多数时候我可以通过搜索 wikipedia/google 和这里​​找到它。

标签: c algorithm probability


【解决方案1】:

首先,您不必担心从ab 的范围。你可以从y 中减去a*x 并假设范围从0b-a。 (因为每个项目至少对总和贡献了 a...所以您可以为每个 x 项目减去 a 一次。)

其次,请注意,您真正想做的是计算获得特定总和的方法的数量。概率就是这个计数除以一个简单的指数(b-a+1)^x

大约十年前,“Ask Dr. Math”涵盖了这个问题:

http://mathforum.org/library/drmath/view/52207.html

他的公式假设骰子从 1 到 X 编号,所以要使用他的答案,您可能希望将范围移动 a-1(而不是 a)以将其转换为该形式。

他的推导使用了生成函数,我觉得应该稍微解释一下。这个想法是定义一个多项式f(z),使得z^n 上的系数 是滚动n 的方式数。例如,对于单个 6 面模具,这是生成函数:

z + z^2 + z^3 + z^4 + z^5 + z^6

...因为从 1 到 6 的每个数字都有一种滚动方式,而其他任何数字都有零滚动方式。

现在,如果你有两个生成函数g(z)h(z) 用于两组骰子,结果证明这些组的并集的生成函数只是gh 的乘积。 (盯着“两个多项式相乘”的操作看一会儿,让自己相信这是真的。)例如,对于两个骰子,我们可以将上面的表达式平方得到:

z^2 + 2z^3 + 3z^4 +4z^5 + 5z^6 + 6z^7 + 5z^8 + 4z^9 + 3z^10 + 2z^11 + z^12

注意我们如何直接从系数中读取组合的数量:1 种方法得到 2 (1*z^2),6 种方法得到 7 (6*z^7),等等。

表达式的立方体会给我们三个骰子的生成函数;第四次方,四个骰子;等等。

当您以封闭形式编写生成函数、相乘,然后使用Binomial Theorem 再次展开它们时,这个公式的威力就来了。详情请听麦博士的解释。

【讨论】:

  • 我很高兴知道其他人很久以前就已经解决了这个问题。但现在我不知道哪个最好用。如果我应该使用 Dr. Math 对 b 做多项式,然后将其提升到第 x 个值。或者如果我应该使用另一个。因为他们似乎以非常相似的方法解决了类似的问题。如果我对生成的代码的理解是写的。不过我不能保证,因为我不是什么编码向导。
  • 这种方法假设每个项目的分布是独立的,不是吗?对于骰子的例子来说似乎是公平的,但这在其他领域不一定是一个聪明/安全的假设。
  • @Mathias:是的。如果概率不是独立的,问题就会变得更难表达,更不用说以封闭形式解决了。
  • 我选择了这个,因为它是我最终在第一篇文章末尾附加的程序中采用的方法。
【解决方案2】:

假设f(a, b, n, x) 表示您可以选择 a 和 b 之间的 n 个数字的方式数,总和为 x。

然后注意:

f(a, b, n, x) = f(0, b-a, n, x-n*a)

确实,只需要一种方法来获得x的总和,然后从n个数字中的每一个中减去a,那么总和将变为x - n*a,并且每个数字都将在0和b-a之间。

因此编写代码找到f(0, m, n, x)就足够了。

现在请注意,实现目标的所有方法,例如最后一个数字是 c 是:

f(0, m, n-1, x-c)

确实,我们还剩下 n-1 个数字,并且希望总和为 x-c。 然后我们有一个递归公式:

f(0,m,n,x) = f(0,m,n-1,x) + f(0,m,n-1,x-1) + ... + f(0,m,n-1,x-m)

右边的和对应最后一个数字,等于 0, 1, ..., m

现在您可以使用递归来实现,但这太慢了。

但是,有一个技巧叫做记忆递归,即你保存函数的结果,这样你就不必再次计算它(对于相同的参数)。

记忆递归的复杂度为O(m * n),因为这是您需要计算和保存的不同输入参数的数量。

计算完计数后,您需要除以可能性总数,即 (m+1)*n 以获得最终概率。

【讨论】:

  • 我想我明白你在说什么。所以你所说的记忆和维基百科解释了。我将该值存储在该值的某个大小的数组中。然后从那时起,我只使用该值而不是重新计算它。因此,例如,如果我要将它存储在一个数组中,我会称之为 R。 R[0] 第一个,R[1] 下一个,依此类推。这似乎也与数学博士相似,但更多的是编程而不是数学。
  • +1 以获得漂亮的答案。三个 cmets: (1) 这种“记忆化”技术也称为动态编程。 (2) 该算法将 m 个数字相加以计算 m×n 表的每个元素,即 O(n*m^2) 加法,而不是 O(m*n)。 (3) 当你除以得到概率时,我认为你的意思是(m+1)^n,而不是(m+1)*n
【解决方案3】:

数论、统计学和组合学让你相信,要得出事件概率的数值——你必须知道两件事:

  • 可能结果的数量
  • 在一组总结果中,有多少个等于您寻求其概率值的结果“y”。

在伪代码中:

numPossibleOutcomes = calcNumOutcomes(x, a, b);
numSpecificOutcomes = calcSpecificOutcome(y);
probabilityOfOutcome = numSpecificOutcomes / numPossibleOutcomes;

然后只需编写上面应该很容易的 2 个函数。

【讨论】:

  • 请格式化您的答案;文字墙很难阅读。 :)
  • 这似乎正是我最终要做的事情。因为它仍然需要我计算每一个可能性。所以无论如何我都必须遍历它们。我一直在寻找一种更快的方法。
【解决方案4】:

要获得所有可能性,您可以制作一个值图:

for (i=a to b) {
 for (j=a to b) {
  map.put(i+j, 1+map.get(i+j))
 }
}

为了更有效地计算总和,您可以使用该模式 6个7,5个6,4个5,3个4,2个3,1个2。

该模式适用于 n x n 网格,将有 n (n+1) 个,总和大于或小于 1 的可能性较小。

这将计算可能性,例如,Count(6, 1/2/3/4/5/6) 将给出骰子总和的可能性。

import math
def Count(poss,sumto):
  return poss - math.fabs(sumto-(poss+1));

编辑:在 C 中,这将是:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>;

int count(int poss, int sumto)
{
  return poss - abs(sumto-(poss+1));
}

int main(int argc, char** argv) {
    printf("With two dice,\n");
    int i;
    for (i=1; i<= 13; i++)
    {
        printf("%d ways to sum to %d\n",count(6,i),i);
    }
    return (EXIT_SUCCESS);
}

给予:

With two dice,
0 ways to sum to 1
1 ways to sum to 2
2 ways to sum to 3
3 ways to sum to 4
4 ways to sum to 5
5 ways to sum to 6
6 ways to sum to 7
5 ways to sum to 8
4 ways to sum to 9
3 ways to sum to 10
2 ways to sum to 11
1 ways to sum to 12
0 ways to sum to 13

【讨论】:

  • 这个答案在 python 中。起初我不确定你是想用伪代码还是 python 来做,因为两者在最基本的形式上几乎和我一样。感谢您向其中添加“导入数学”,我现在知道它是 python,因此对我来说不是那么有用(至少)。我对 python 的理解仅限于查看代码并能够理解某些函​​数是如何工作的。我知道语法,我可能会写一点,但它离我最喜欢也不是最知名的语言很远。
猜你喜欢
  • 1970-01-01
  • 2011-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多