在 数论及Python实践一文中,我们介绍了组合的基本定义以及一些常规实现方法,并未充分发挥python语言的优势,本文我们从reduce
函数的角度(从这个角度我们应当恢复reduce
正宫娘娘的地位,因为在python3中Guido将reduce
从系统内置函数降格为functools
中的函数),重新实现给出排列组合的各自实现,以及据此给出”生日问题”的概率解释。
第二行公式可看做其递归定义式。我们换个记号继续推导:
这里还有一个经典的结论:
如何证明,其实很简单,回归定义(组合数combination,又叫二项式系数binomial coefficients),对 2 n 进行二项展开,即:
2 n
这不正是二进制嘛!这个结论有什么用?举个栗子,给定三个卡片,编号为
1−3
,使用该等式可得其会有
2 3 =8
种组合(combinations或叫subsets)(包括空集):
以二进制的形式理解的话即为:
- 0:000
- 1:001
- 2:010
- 3:011
- 4:100
- 5:101
- 6:110
- 7:111
再来考虑这样一种情形,从
n
个数中随机选择
将此记为: (nk,p) ,也即 (nk,p)=n!k!p!(n−k−p)!
再来看几个结论:
所以有:
左边允许重复,右边不允许重复;
当我们试图用reduce
实现组合数的计算时,
from functools import reduce
import operator
def fac(n):
return reduce(operator.mul, range(1, n+1), 1)
# 阶乘n!的定义
# reduce与operator.mul结合
def perm(n, k):
return reduce(operator.mul, range(n-k+1, n+1), 1)
# 排列数的定义
def comb(n, k):
return perm(n, k)//fac(k)
def test():
print('{}!={}'.format(5, fac(5)))
print('A_{}^{}={}'.format(5, 2, perm(5, 2)))
print('C_{}^{}={}'.format(5, 2, comb(5, 2)))
if __name__ == '__main__':
test()
运行结果为:
5!=120
A_5^2=20
C_5^2=10
由以上准备,我们可求解概率论史上的经典问题(概率论史上的经典问题一般是指违反直觉的那些问题)”生日问题”:
一次聚会上,只要有23个人,就有50%的可能性其中至少有两个人生日相同,如果人数达到50人,至少有两个人生日相同的概率达到97%。(这个结论很恐怖,只要是班上的人数超过50,老师便可以说,我们班至少有两个人生日相同,其实在人数超过23的时候,我们便可以这么说,应为概率占优,注意,是班上会有来个人生日相同,不是说,班上至少存在一个人生日生日与我相同)。
当然这类问题从其反问题对立事件出发,
1−P(所有人都不在同一天)=P(至少有两人在同一天)
,
这里的
A 50 365
可以理解为,随意指定一个生日,他生日所在的自由度
(或者作可选空间)为365,则下一个人只有365-1的自由度
,依次类推。
365 50
是考虑到这50个人的生日大体独立,也即每一个的生日都有365个自由度
(也即365种选择)。所谓概率,频率的观点(另有贝叶斯的观点)来看就是出现的次数与总的可能性之比。
>>> 1-perm(365, 23)/(365**23)
0.5072972343239854
>>> 1-perm(365, 50)/(365**50)
0.9703735795779884
注意:如果预先指定一个生日,随机选取125人,250人,500人,出现某人生日正好是这一生日的概率分别是:
比想象的要小很多,再次说明概率中的许多问题都比较违反直觉。
补充:
为什么不等于呢?在于,左边 “允许重复”(同一个位置,既可以是你,也可以是他),右边 不允许重复;