二项分布
问题描述:
二项分布就是重复n次独立的伯努利试验。在每次试验中只有两种可能的结果,而且两种结果发生与否互相对立,并且相互独立,与其它各次试验结果无关,事件发生与否的概率在每一次独立试验中都保持不变,则这一系列试验总称为n重伯努利实验,当试验次数为1时,二项分布服从0-1分布。
假设伯努利实验中事件发生的概率为p,求n次独立的伯努利实验,事件发生k次的概率。
数学公式
我们都知道,上述问题可采用公式(1):
P(X=k)=Cknpk(1−p)n−k式(1)
计算,其中
Ckn=n!k!(n−k)!式(2)
我们还知道:
Ckn=n!k!(n−k)!=(n−1)!k!(n−1−k)!+(n−1)!(k−1)!(n−k)!=Ckn−1+Ck−1n−1式(3)
于是:
Cknpk(1−p)n−k=(1−p)Ckn−1pk(1−p)n−1−k+pCk−1n−1pk−1(1−p)n−k式(4)
我们记
f(n,k)=Cknpk(1−p)n−k式(5)
于是我们有:
f(n,k)=(1−p)f(n−1,k)+pf(n−1,k−1)式(6)
算法:
递归算法:
算法思想:
递归算法思想就是来源于上面的公式(6),我们只需要找到递归出口,也就是最基本的情况就行了。
很明显:我们有
f(n,k)=⎧⎩⎨⎪⎪(1−p)f(n−1,k)+pf(n−1,k−1)ifn>k,k>01.0 ifk=n=0式(7)0.0 ifk>n或者n<0或者k<0
递归算法代码:
/**
* 时间复杂度:O(2^n)
* @param N
* @param K
* @param p
* @return 伯努利实验n次,事件发生k次的概率
*/
public static double binomialRec(int N, int K, double p) {
if (N == 0 && K == 0)
return 1.0;
if (N < 0 || K < 0)
return 0.0;
return (1.0 - p) * binomialRec(N - 1, K, p) + p * binomialRec(N - 1, K - 1, p);
}
非递归算法
算法思想:
上述递归算法的时间复杂度太高,当n比较大时,可能都运行不出结果。之所以时间复杂度太高,是因为有大量的重复计算,于是很直观的想法就是,把已经算出来的结果存储到数组里。由于是计算实验n次,事件发生k次的概率,所以我们可以用二维数组存放计算的中间结果。
非递归算法代码:
/**
*
* 伯努利实验,实验n次,出现k次的概率
* 非递归方式实现,采用二维数组存储已经计算过的值
* 时间复杂度:O(n * k),空间复杂度:O(n * k)
* @param n
* @param k
* @param p
* @return
*/
public static double binomialArray(int n, int k, double p) {
double[][] array = new double[n+1][k+1];
// Initilize array
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= k; j++) {
array[i][j] = 0;
}
}
array[0][0] = 1.0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= k; j++) {
if (j > i) {
break;
}
if (i - 1 >= 0) {
array[i][j] += (1.0 - p) * array[i-1][j];
if (j - 1 >= 0) {
array[i][j] += p * array[i-1][j-1];
}
}
}
}
return array[n][k];
}
非递归算法改进
算法思想: 上述非递归算法中,我们使用的是二维数组存放计算过的值,但我们发现,在计算array[i][j]时,只与其上一行的array[i-1][j]和array[i-1][j-1]有关,与其他值无关,所以我们可以使用一维数组来实现。这样空间复杂度可以降低到O(k).
非递归算法改进代码:
/**
* 伯努利实验,实验n次,出现k次的概率
* 非递归方式实现,采用一维数组实现存储计算过的值
* 时间复杂度:O(n * k),空间复杂度:O(k)
* @param n
* @param k
* @param p
* @return
*/
public static double binomial(int n, int k, double p) {
double[] array = new double[k+1];
for (int i = 0; i < array.length; i++) {
array[i] = 0.0;
}
array[0] = 1.0;
for (int i = 1; i <= n; i++) {
// 这里要倒着计算,因为正序计算新值会覆盖掉之前的旧值
for (int j = k; j >= 0; j--) {
if (j - 1 >= 0) {
array[j] = (1.0 - p) * array[j] + p * array[j-1];
}
else {
array[j] = (1.0 - p) * array[j];
}
}
}
return array[k];
}