生日悖论是真的吗?
生日悖论是真的吗?
生日悖论是概率论中的一个经典问题:在一个只有23人的房间里,有50%的概率至少有两个人同一天生日。这个结论看似违反直觉,但通过编程模拟可以验证其真实性。本文将通过Python代码实现生日悖论的模拟实验,并探讨其背后的数学原理。
生日悖论如下:在一个只有23人的房间里,有50:50的机会至少有两个人同一天生日。
为什么这是一个悖论?因为另一个人与你同一天生日的概率是1/365。我不认识任何与我同一天生日的人。(当然,除了我的双胞胎!)既然我遇到的人远远超过23个,这怎么可能是真的?
这种推理是有问题的,首先问题并不是在问房间里是否有另一个人与你同一天生日——任何一对人(或更多)都可以共享生日,从而增加声明成立的机会。
完整的答案涉及很多数学,但我想通过模拟实验来向你展示它是如何成立的。
模拟是让计算机或模型像真实事件一样运作。通常,你会这样设置,以便模拟的成本远低于实际操作。例如,将模型飞机机翼放入风洞中就是一种模拟。
我用一种叫做Python的编程语言模拟了生日悖论。确实,这比在一个有23人的房间里要容易得多。
模拟
将我们需要询问直到得到重复生日的人数称为n。这就是所谓的随机变量,因为它的值是未知的,可能会因我们无法控制的条件(如房间里有谁)而变化。
现在,我们模拟一个实验来实现n值,如下所示:
- 随机选择一个人并询问他们的生日。
- 检查是否有人给出了相同的答案。
- 重复步骤1和2,直到某个生日被说了两次。
- 统计被询问的人数,并称之为n。
下面是模拟这个实验单实例的代码:
import numpy as np
def collect_until_repeat(dist = np.ones(365)/365):
# dist 是生日分布。如果未知,假设为均匀分布
# 我们将用这个变量作为停止条件
repeat = False
# 我们将把生日存储在这个向量中
outcomes = np.zeros(dist.shape[0])
# n 将统计我们遇到的人数。每个循环是一个新的人。
n = 0
while not repeat:
# 计数器加一
n += 1
# 模拟遇到一个随机生日的人
outcomes += np.random.multinomial(1,dist)
# 检查是否得到重复
if np.any(outcomes > 1):
repeat = True
return n
到达步骤4构成一次实验。输出的数字可能是n = 2或n = 100。这完全取决于房间里有谁。因此,我们多次重复所有步骤,并观察数字的分布。重复次数越多,我们获得的数据越多,对正在发生事情的理解就越好。
这是我们运行一百万次实验时的样子。
那么,这些数字意味着什么呢?好吧,让我们看看n=2出现了多少次。例如,在这100万次试验中,结果2出现了2726次,相对比例为0.2726%。
注意,这接近于1/365 ≈ 0.274%,因为第二个人与第一个人同一天生日的概率正好是1/365。出现次数除以一百万大致就是我们需要询问多少人才能找到共享生日的概率。
将这些条形图上的值相加,总和为1,000,000,即总实验次数。
现在,我们可以从n=2开始累加这些数字,直到占据50%的总实验次数(在这种情况下为500,000)。
从视觉上看,我们正在寻找将上面的彩色区域分成两等份的数字。这个数字将是我们需要遇到的人数,以便有50:50的机会找到重复生日。
你能猜到会是多少吗?
滚鼓声……23!哇!通过模拟解决了生日悖论!
但等等!还有更多
那些闰年出生的人呢?事实上,假设生日均匀分布是否错误?如果我们在现实生活中真正尝试这个实验,会得到23还是其他数字?
幸运的是,我们可以用真实数据来检验这个假设!至少对于美国出生的数据,你可以在FiveThirtyEight’s GitHub页面找到。这是实际分布的样子。
这些数据可以以几种方式绘制。在这里,你可以更轻松地选择你的生日,并查看排名。
看起来并不均匀。例如,显然12月25日和12月31日出生人数大幅下降。
12月25日出生人数不多这一事实应该意味着在剩下的日子里更容易找到共享生日。这会影响生日悖论吗?让我们通过使用实际生日分布来模拟实验来检验这个假设。
为此,我们执行与上面相同的4个步骤,但从实际分布中随机抽样答案。另一次100万次实验后的结果如下图所示。
答案还是一样!即使使用实际生日分布,生日悖论依然存在。
差一点!
如果你是那些对“接近”生日感到兴奋的人之一,我有好消息!正如你可能预料到的,如果我们只需要找到两个“接近”生日的人,我们只需要一个更少人数的房间。
我们需要稍微修改实验,以允许“容差”来衡量接近程度。如果tolerance=0,我们要求相同的生日;如果tolerance=1,我们允许一天之差;依此类推。
- 随机选择一个人并询问他们的生日。
- 检查是否有人给出了在容差范围内的生日。
- 重复步骤1和2直到成功。
- 统计被询问的人数,并称之为n。
代码如下:
import numpy as np
def correct_until_near_repeat(dist=np.ones(365)/365, tolerance=0):
# 我们将把生日存储在这个向量中
outcomes = np.zeros(dist.shape[0])
# n 将统计我们遇到的人数。每个循环是一个新的人。
n = 0
while True:
# 计数器加一
n += 1
# 模拟遇到一个随机生日的人
outcomes += np.random.multinomial(1, dist)
# 检查是否得到重复或接近重复
for i in range(len(outcomes)):
# 检查当前日期的生日计数是否大于1(精确重复)
if outcomes[i] > 1:
return n
# 如果tolerance > 0,检查接近重复
if tolerance > 0:
# 在容差窗口内检查
for j in range(1, tolerance + 1):
# 环绕数组以处理循环日期(因为生日在一年中循环)
if outcomes[i] > 0 and (outcomes[(i + j) % 365] > 0 or outcomes[(i - j) % 365] > 0):
return n
经过另一次100万次实验后,在不同容差下得到以下表格。
换句话说,如果你允许“接近”生日,你几乎可以肯定在大约20人的房间里找到一对。