"锦鲤"抽奖活动背后的随机抽样算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_29027865/article/details/83417245

算法菜鸡一枚,如叙述有误,欢迎大佬拍砖指教

最近"锦鲤"活动很盛行,那么在思考锦鲤活动背后抽奖的逻辑到底是怎样的?

不妨我们先来将问题抽象化:从N个样本中随机不重复地抽取K个样本,其中N的长度很大或者未知,如何等概率或者不等概率(因为有些商家活动有助力功能,助力的票数越多,中奖的概率越大)的抽取出K个样本;

1.相同概率

(一)抽签抽样:

简述:

首先容易想到的是抽签抽样。即当由 k 个人抽 k 张签,无论先后顺序,每个人抽中的概率都是1/k。同理, k 个人抽 N 张签,无论先后顺序,每个人抽中的概率都是1/N 。

原理解释如下:

1、当k=1时,表示从N个样本中等概率的抽取一个,所以抽中的概率是1/N;

2、当k=2时,表示从N个样本中等概率的抽取两个,在抽取一个后,在剩下的 N-1 个中随机选,由于第1次没有选中它,所以它的概率应为:第一步没有抽中我的概率 * 第二步抽中我的概率,因此概率为:(N-1)/N * 1/(N-1) = 1/N。

3、当k=3时,同理,概率为 (N-1)/N * (N-2)/(N-1) * 1/(N-2) = 1/N。

由归纳法可得所有每个人抽中的概率都为1/N;

扫描二维码关注公众号,回复: 4080635 查看本文章

实现上,我们可以用交换排序的思想对N进行k次抽签操作:

遍历k,从0到k-1开始,

第一次对0-N-1个元素中取随机值,然后将此随机值与第一个元素(第0个位置)上的元素交换;

第二次从下一个位置开始到N-1,然后将此随机值与这个位置上的元素进行交换;

直至到k-1个值,此时前K个值得到的即为抽中的;

实现效果如下:

代码如下:

import random

def SelectRandomK(L, N, k):

    for i in range(0, k):
        # 产生i到N-1间的随机数
        j = random.randint(i, N - 1)
        L[i], L[j] = L[j], L[i]
    return L
if __name__ == '__main__':
    L = ['令狐冲','任盈盈','左冷禅','任我行','岳不群','林平之','东方不败','田伯光','风清扬','方证大师','冲虚道长','向问天']
    N = len(L)
    k = 2
    L = SelectRandomK(L,N,k)
    for i in range(2):
        print(L[i])

(二)蓄水池抽样:

简述:

抽签算法需要依赖于数据总数N,那么如果实际过程中N的数目巨大,不知道实际的N值该怎么处理?那么就可以用在不知道数据总数目的情况下完成随机抽样的蓄水池抽样。

先按抽一个来考虑,即先随机把一个样本以全概率1/1作为蓄水池存进去;

然后第二个样本来时,以1/2的概率替换蓄水池中的数据;

第三个样本来时,以1/3的概率来替换蓄水池中的数据,直至遍历完所有的数据。

原理解释如下:

这个方法能不能行得通,关键在于每个样本替换的概率是否为1/n,上面的解释,很容易让某些同学产生误解,即越往后来的样本越小,那么概率去替换那一个值,岂不是后面中奖(替换)的概率要小的多?

在解释之前,我们先来说一个小例子,即  条件概率的独立性

先照抄一下公式 --> P(A₁A₂|A)=P(A₁|A)•P(A₂|A)

独立性指的是,当A发生时,A₁发生与否与A₂发生与否是无关的。不如我们这样考虑:我们10个人去买彩票,彩票号是根据人数随机抽的(注意这里的彩票相当于是有放回),我第一个去抽,中奖概率1/10,结果没有中奖,虽然我没有中奖,但不影响第5个,第6个去抽的中奖概率。

理解到这,我们就可以通过数学归纳法来进行证明了,来证明每个数据被抽中的概率是相等的,即使不知道数据总数目。

只需要证明当遍历至第k个时,前面k个中的任意一个被抽取的概率均为1/k。

证明:(1)当i=1时,第一个被抽取的概率为1/1;

        (2)假设当i=k时成立,则前面k个中的任意一个被抽取的概率为1/k。

现证明i=k+1时成立,当i=k+1时,第k+1个替换原有样本的概率为1/(k+1),所以第k+1个被抽取(中奖)的概率是1/(k+1)。

前面k个任意一个被抽取的概率为:自己中奖的概率 * 其他未中奖的概率,即为:1/k*k/(k+1)=1/(k+1)

       即当i=k+1时成立。

实现效果如下:

代码如下:

import random

def SelectRandom1(L):
    # 计数器
    num = 2
    for item in L[1:]:
        if random.random() < 1.0/num:
            L[0], L[num - 1] = L[num - 1], L[0]
        num += 1
    return L[0]
if __name__ == '__main__':
    L = ['令狐冲', '任盈盈', '左冷禅', '任我行', '岳不群', '林平之', '东方不败', '田伯光', '风清扬', '方证大师', '冲虚道长', '向问天']
    res = SelectRandom1(L)
    print(res)

那么,如何用蓄水池来等概率的选择k个数呢?

跟一个数类似,实现方法如下:

先把前k个数据放入蓄水池,对第k+1个数据,我们以 k/(k+1)概率决定是否要把它放入蓄水池,放入时随机的选取一个作为替换项。对第n个数据,我们以 k/n概率决定是否要把它放入蓄水池,放入时随机的选取一个作为替换项。这样一直做下去,直至遍历完所有的样本空间。可以证明,对于任意的样本空间N,对每个数据的选取概率都为k/N。

数学归纳法证明与上面类似,就不做说明了。

实现代码如下:

def SelectRandomK(L, k):
    # 计数器
    num = k + 1
    for item in L[k:]:
        if random.random() < float(k/num):
            # 产生0到k-1之间的随机数
            j = random.randint(0, k - 1)
            L[num - 1], L[j] = L[j], L[num - 1]
        num += 1
    return L
if __name__ == '__main__':
    L = ['令狐冲', '任盈盈', '左冷禅', '任我行', '岳不群', '林平之', '东方不败', '田伯光', '风清扬', '方证大师', '冲虚道长', '向问天']
    k = 3
    L = SelectRandomK(L, k)
    for i in range(3):
        print(L[i])

2.不相同概率

       对于不相同概率,可以对应于某项,增加其在列表中的出现次数,也可以在random中,使其控制在一个较高的随机数范围进行解决。其他算法问题再陆续续加。

猜你喜欢

转载自blog.csdn.net/qq_29027865/article/details/83417245