本系列同步发布于本人的知乎专栏:确定性随机
个人觉得随机模拟有一个很大的优势,那就是用类似于思想实验的方式对理论进行验证,同时也能够解决很多理论上无法最终解析的事情,给出一个近似但很实际用处的结论。
3. 优惠券收集问题
曾经在知乎上看过这样一个问题:
话说古代中国帝王都是后宫佳丽三千,问若每天晚上皇帝都随机地宠幸一位妃嫔,问需要多少天才能将三千妃嫔都宠幸一遍?
这个问题就是典型的所谓优惠券收集问题。上面那个问题和小时候吃脆面攒齐梁山水浒一百单八将平均需要吃多少包脆面呢。我们可以假设,当前我们已经凑了 个水浒人物,假设要抽到 人物,需要再消费 包干脆面。在消费者 包干脆面中,我们抽到重复卡片的概率 :
,
我们成功抽到 个人物角色时,一共失败了 次,符合几何分布:
所以,你要集齐108个梁山好汉,那么你要吃掉的(或者买)干脆面的数量为:
,
期望你要吃500多包才能凑够。(同样,我们可以计算那个皇帝宠幸3000妃嫔的题目)。当然,这都是理想情况,我们假设的是每个卡片出现的概率是一样的,但实际情况是商家会故意让某些卡片出现的概率很低,从而就会增加购买的次数。
现在我们将问题反过来一下,为了简化下问题,我们把问题改为集齐6张优惠券而不是108个英雄好汉卡,现在问题是我买了12包脆面,那么我能够集齐6张优惠券的概率是多少呢?其实这个是一个累计概率的计算问题,设 为第 次集齐6张优惠券的概率,显然有 ,于是我们的原问题就是求解:
这个问题直接求解的难度较高,我们可以用随机模拟的方式来解决这个问题。我们考虑1-6为数字平均分布,每次连续获取12个随机数,如果这12个随机数里面包含完整的1-6共6个数字,那么就认为这12次的抽券获得了完整的优惠券组合。Python代码如下(虽然这本书要求使用的是Matlab,这个我之后再了解下吧:P):
import numpy as np def simulate_lottery(num_of_take): ret = 0 upper = 6 # 完整优惠券组合的数量 lottery_number_set = set() for n in range(num_of_take): rand_lottery_number = np.random.randint(1,upper+1) lottery_number_set.add(rand_lottery_number) len_of_set = len(lottery_number_set) if len_of_set == 6: ret = 1 print "collect a different cards" else: ret = 0 print "this card is what i have already owned" return ret num_of_experiences = 1000 num_of_lottery_to_take = 12 #抽取优惠券的次数## completed_collect = 0 for i in range(num_of_experiences): completed_collect += simulate_lottery(num_of_lottery_to_take) print completed_collect
重复做了1000次实验,得到的结果是431, 也就是说连续抽12次,能够集齐优惠券的概率大概是43.1%。下面的问题是,随着抽的次数增加,能够集齐优惠券的概率是如何变化的呢。对上面的代码稍微修改下:
1 # -*- coding: utf-8 -*- 2 import numpy as np 3 4 def simulate_lottery(num_of_take): 5 ret = 0 6 upper = 6 7 lottery_number_set = set() 8 for n in range(num_of_take): 9 rand_lottery_number = np.random.randint(1,upper+1) 10 lottery_number_set.add(rand_lottery_number) 11 len_of_set = len(lottery_number_set) 12 if len_of_set == 6: 13 ret = 1 14 print "collect a different cards" 15 else: 16 ret = 0 17 print "this card is what i have already owned" 18 return ret 19 20 num_of_experiences = 1000 21 #num_of_lottery_to_take = 12 22 completed_collect = 0 23 pro_ret = {} 24 25 for num_of_lottery_to_take in [12,20,25,35,45,50,60]: #定义抽取的次数 26 for i in range(num_of_experiences): 27 completed_collect += simulate_lottery(num_of_lottery_to_take) 28 pro_ret[num_of_lottery_to_take] = completed_collect 29 print pro_ret
结果是:
{35: 982, 12: 421, 45: 998, 50: 1000, 20: 846, 25: 956, 60: 1000}
可见,抽到25次以后,能收集完整奖券的概率已经升到95.6%。