经典概率题等概率取数/蓄水池问题等

 

题目:给一副扑克牌和一个随机数函数,设计一个洗牌算法。

解析:
最直观的思路是什么?很简单,每次从牌堆中随机地拿一张出来。那么, 第一次拿有52种可能,拿完后剩下51张;第二次拿有51种可能,第三次拿有50种可能, …,一直这样随机地拿下去直到拿完最后1张,我们就从52!种可能中取出了一种排列, 这个排列对应的概率是1/(52!),正好是题目所要求的。 

接下来的问题是,如何写代码去实现上面的算法?假设扑克牌是一个52维的数组cards, 我们要做的就是从这个数组中随机取一个元素,然后在剩下的元素里再随机取一个元素… 这里涉及到一个问题,就是每次取完元素后,我们就不会让这个元素参与下一次的选取。 这个要怎么做呢。 

我们先假设一个5维数组:1,2,3,4,5。如果第1次随机取到的数是4, 那么我们希望参与第2次随机选取的只有1,2,3,5。既然4已经不用, 我们可以把它和1交换,第2次就只需要从后面4位(2,3,1,5)中随机选取即可。同理, 第2次随机选取的元素和数组中第2个元素交换,然后再从后面3个元素中随机选取元素, 依次类推。
代码:

#include <iostream>
#include <cstdlib>
using namespace std;
 
void Swap(int &a, int &b){// 有可能swap同一变量,不能用异或版本
    int t = a;
    a = b;
    b = t;
}
void RandomShuffle(int a[], int n){
    for(int i=0; i<n; ++i){
        int j = rand() % (n-i) + i;// 产生i到n-1间的随机数
        Swap(a[i], a[j]);
    }
}
int main(){
    srand((unsigned)time(0));
    int n = 9;
    int a[] = {
        1, 2, 3, 4, 5, 6, 7, 8, 9
    };
    RandomShuffle(a, n);
    for(int i=0; i<n; ++i)
        cout<<a[i]<<endl;
    return 0;
}

 

讲义讲解版本:

for i in 1...n

    randomly select a card j from [1, i]

    swap card i with card j 

证明:

我们使⽤用数学归纳法进⾏行证明算法1的正确性:

每张牌出现在各个位置的概率相等(1/N)

当N = 1时,显然成⽴立 

当N=2,每张牌出现在两个位置的概率都是1/2 

假设当N=k时候成⽴立,我们现在证明N = k +1的时候也成⽴立,即每张牌出现出现在各个位置的概率均为1/(k + 1)

我们分三部分来看:第k+1张牌到所有位置,前k张牌到第k+1个位置,前k张牌到前k个位置 

第k+1张牌到所有位置
显然,第k + 1张牌到所有位置的概率均为1/(k +1),算法就是这么写的:) 

前k张牌到第k+1个位置 

跟上⾯面同理,前k张牌被交换到第k + 1个位置的概率为1/(k +1)

前k张牌到前k个位置(最复杂):
我们知道N = k的时候成⽴立,所以不考虑第k+1张牌,前k张牌在前k个位置的概率是1/k

但现在,因为第k+1张牌的出现,我们还要保证前k张牌中的某⼀一张不被交换⾛走,这个概率是:(1 - 1/(k + 1))

所以根据⻉贝叶斯公式,最终的概率为:
1/k * (1 - 1/(k + 1)) = 1/(k + 1) 

 

转载于:https://www.cnblogs.com/nily/p/4815797.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值