看到一个非常有趣的题目,学会了一个非常巧妙的算法:
如何从n个数中随机的选择k个数?
如果采用直接产生随机数的方法,当n和m都很大时,随机数产生重复的概率会很大,算法性能会大大降低。
这里采用蓄水池算法,算法代码很简答,假设从n个数中选择k个数:
for (int i=k+1;i<=n;i++)
{
int p=rand(1,i);
if (p<=k)
swap(i,p);
}
解释一下算法是如何工作的:
先假设只有k个数,前k个数都已经被选择进入备选池,备选池内的数保证当前所有数据每个数被选择的概率相同。
这里使用数学归纳法来证明:
对第k+1个数:
被选择到进行交换的概率为k/(k+1),即发生替换的概率为k/(k+1)。
对于备选池内的每个数,被替换的概率为1/k,即备选池内每个数被替换的概率为k/(k+1) * 1/k = 1/(k+1),留下的概率为1-1/(k+1)=k/(k+1)。
假设第k+t个数成立,对k+t+1个数来说:
被选择到进行交换的概率为k/(k+t+1),即发生替换的概率为k/(k+t+1)。
对于备选池内的每个数,一个数在备选池内的概率为k/(k+t),被替换的概率为1/k,即备选池内每个数被替换的概率为k/(k+t+1) * 1/k = 1/(k+t+1),
留下的概率为(k/(k+t)) * (1-1/(k+t+1)) = k/(k+t+1)。
证明完毕。