趣味编程:三门问题

三门问题,也称为蒙提霍尔问题(Monty Hall Problem)。html

你在参加一个节目,面前是三扇关闭着的门。其中一扇后面是小汽车,选中它就可赢得汽车,另外两扇后面各是一只羊。你选择了其中一扇,但没有打开它,这时主持人打开了剩下两扇门中的一扇,后面是一只山羊(这里有个隐含前提:主持人是知道门后的状况的)。主持人问你,要不要换另外一扇仍然关闭着的门,仍是就要你刚才选中的那扇。app

那么问题就是,换另外一扇门会增长你赢得汽车的几率么?换与不换的几率各是多少呢?dom

三门

由于只剩下了两扇门,其中有一车和一羊,所以答案是换不换几率都是1/2,对么?ide

也有人坚信不换的几率是1/3,那么换的几率就应该是2/3?不管哪一种回答,必定都会有本身的解释和逻辑。spa

 

什么是几率?无非就是某种事件发生的可能性。3d

如何验证几率?只有用大量的实验来统计各类事件发生的分布状况。code

放到现实中,想看到这个问题的答案,只能由主持人和观众不断的重复进行游戏。orm

看看好比说各自100次游戏,不换门会选中多少次,换门又会选中多少次。htm

 

这就体现出了代码的优点,无需舞台无需观众无需主持人,也无需一遍又一遍的重复。blog

让咱们直接抛开语义和逻辑上的争论,让事实来讲话。

 

彻底忠实于游戏的规则来实现:

 1 import random
 2 import logging
 3 
 4 class MontyHall(object):
 5     def __init__(self, num=3):
 6         '''
 7         建立一个door列表
 8         0表示门关闭的状态
 9         1表示该门后有车
10         -1表示该门被主持人打开
11         '''
12         self.doors = [0] * num
13         self.doors[0] = 1
14         self.choice = -1
15         self.shuffle()
16 
17     def shuffle(self):
18         '''
19         开始新的游戏
20         关闭全部打开的门(-1)
21         从新安排轿车的位置
22         '''
23         for i in range(len(self.doors)):
24             if self.doors[i] == -1:
25                 self.doors[i] = 0
26         random.shuffle(self.doors)
27 
28     def makeChoice(self):
29         '''
30         player随机选择一扇门
31         '''
32         self.choice = random.randint(0, len(self.doors)-1)
33         logging.info('choice: %d' % self.choice)
34         logging.info('original: %s' % self.doors)
35 
36     def excludeChoice(self):
37         '''
38         主持人排除选择
39         直到只剩两扇门
40         '''
41         toBeExcluded = []
42         for i in range(len(self.doors)):
43             if self.doors[i] == 0 and i != self.choice:
44                 toBeExcluded.append(i)
45 
46         random.shuffle(toBeExcluded)
47         for i in range(len(self.doors)-2):
48             self.doors[toBeExcluded[i]] = -1
49         logging.info('final: %s' % self.doors)
50 
51     def changeChoice(self):
52         '''
53         player改变选择
54         '''
55         toChange = []
56         for i in range(len(self.doors)):
57             if i != self.choice and self.doors[i] != -1:
58                 toChange.append(i)
59         self.choice = random.choice(toChange)                
60         logging.info('choice changed: %d' % self.choice)
61 
62     def showAnswer(self):
63         logging.info(self.doors)
64 
65     def checkResult(self):
66         gotIt = False
67         if self.doors[self.choice] == 1:
68             gotIt = True
69         return gotIt

 

不改变选择:

 1 def test(n):
 2     result = {}
 3     game = MontyHall()
 4 
 5     for i in range(n):
 6         game.shuffle()
 7         game.makeChoice()
 8         game.excludeChoice()
 9 
10         if game.checkResult():
11             result['yes'] = result.get('yes', 0) + 1
12         else:
13             result['no'] = result.get('no', 0) + 1
14 
15     for key in result:
16         print('%s: %d' % (key, result[key]))
17     
18 
19 
20 if __name__ == '__main__':
21     logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.WARNING)
22     test(10000)
View Code
yes: 3304
no: 6696

 

改变选择:

 1 def test(n):
 2     result = {}
 3     game = MontyHall(3)
 4 
 5     for i in range(n):
 6         game.shuffle()
 7         game.makeChoice()
 8         game.excludeChoice()
 9         # 改变选择
10         game.changeChoice()
11 
12         if game.checkResult():
13             result['yes'] = result.get('yes', 0) + 1
14         else:
15             result['no'] = result.get('no', 0) + 1
16 
17     for key in result:
18         print('%s: %d' % (key, result[key]))
19     
20 
21 
22 if __name__ == '__main__':
23     logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.WARNING)
24     test(10000)
View Code
yes: 6691
no: 3309

 

可见,若是不改变,选中的几率是1/3。若是改变,选中几率为2/3。因此说,最佳策略是换门。

 

从逻辑上如何解释呢?

若是你每次都换,只有当你第一次选的那扇门后是汽车时,你才会输。

由于第一次选中汽车的几率是1/3,因此换门后输的几率是1/3。

也就是说,若是你每次都换,赢的几率就有2/3。

 

还不信么?

那咱们换成50扇门再作这个游戏。你选一扇门,我把其余是羊的48扇门打开给你,最后依然剩下两扇门,你还会以为换和不换的几率同样是1/2么?

依然以为在50扇门中任选一个,最后中将的几率是1/2?

 

五十门

 

原理是同样的,只有你第一次就选中汽车时(1/50几率),换门才会失去大奖。其余的状况,换门都会让你赢得大奖,几率为49/50。

再次用代码来验证:

 1 def test(n):
 2     result = {}
 3     game = MontyHall(50)
 4 
 5     for i in range(n):
 6         game.shuffle()
 7         game.makeChoice()
 8         game.excludeChoice()
 9         game.changeChoice()
10 
11         if game.checkResult():
12             result['yes'] = result.get('yes', 0) + 1
13         else:
14             result['no'] = result.get('no', 0) + 1
15 
16     for key in result:
17         print('%s: %d' % (key, result[key]))
18     
19 
20 
21 if __name__ == '__main__':
22     logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.WARNING)
23     test(10000)
View Code
yes: 9794
no: 206

 

依然不相信?

逻辑分析和事实数据都不能让你相信?仍是认为最后的几率都是1/2?

那我只好遗憾的表示,三门问题的答案是肯定的,不存在任何争议。

本身去科普一下吧,不要困在本身的局限的认知里。

 

附上一个科普节目,让大名鼎鼎的流言终结者(S09E21)来扫盲吧。

 

若是逻辑分析+实验事实+科普节目都没法让你放弃1/2的结论,那我真无能为力了:)