考虑以下算法。
function Rand():
return a uniformly random real between 0.0 and 1.0
function Sieve(n):
assert(n >= 2)
for i = 2 to n
X[i] = true
for i = 2 to n
if (X[i])
for j = i+1 to n
if (Rand() < 1/i)
X[j] = false
return X[n]
Sieve(k)作为k的函数返回true
的概率是多少?
答案 0 :(得分:5)
让我们递归地定义一系列随机变量:
设X k,r 表示指标变量,在变量1
取值的迭代结束时取值X[k] == true
iff i
r
。
为了获得更少的符号,并且由于它使代码更直观,我们只需编写X k,i 这是有效的,尽管在{{{{{当第一个引用循环中的变量而后者引用变量的值时,取值i
会引起混淆。
现在我们注意到:
P(X k,i ~0)= P(X k,i-1 ~0)+ P(X k,i-1 ~1)* P(X k-1,i-1 ~1)* 1 / i
(〜代替=只是为了让它变得可以理解,因为=否则会有两个不同的含义,看起来很混乱)。
由于在i
迭代结束时X[k]
false
为i
,因此在i-1
结束时它是假的,因此这种平等成立,或者在那时它是true
,但是在最后一次迭代中X[k-1]
是true
,因此我们进入循环并以1 / i的概率更改X[k]
。事件是互斥的,所以没有交集。
递归的基础就是P(X k,1 ~1)= 1而P(X 2,i ~1)= 1的事实
最后,我们只注意到P(X[k] == true
)= P(X k,k-1 ~1)。
这可以很容易地编程。这是一个使用memoisation的javascript实现(如果使用嵌套索引比字典索引的字符串连接更好,你可以进行基准测试,你也可以重新设计计算以保持相同的运行时复杂性,但不要通过构建自下而上的堆栈大小来耗尽堆栈大小不是自上而下的)。当然,实现的运行时复杂度为O(k^2)
,因此对于任意大的数字都不实用:
function P(k) {
if (k<2 || k!==Math.round(k)) return -1;
var _ = {};
function _P(n,i) {
if(n===2) return 1;
if(i===1) return 1;
var $ = n+'_'+i;
if($ in _) return _[$];
return _[$] = 1-(1-_P(n,i-1) + _P(n,i-1)*_P(n-1,i-1)*1/i);
}
return _P(k,k-1);
}
P(1000); // 0.12274162882390949
更有趣的是1 / i概率如何改变事物。即概率是否收敛于0或某个其他值,如果是,则改变1 / i如何影响该概率。
当然,如果你问数学,你可能会得到一个更好的答案 - 这个答案非常简单,我确信有一种方法可以操纵它来获得一个直接的公式。