三门问题,也称为蒙提霍尔问题(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)
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)
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)
yes: 9794 no: 206
依然不相信?
逻辑分析和事实数据都不能让你相信?仍是认为最后的几率都是1/2?
那我只好遗憾的表示,三门问题的答案是肯定的,不存在任何争议。
本身去科普一下吧,不要困在本身的局限的认知里。
附上一个科普节目,让大名鼎鼎的流言终结者(S09E21)来扫盲吧。
若是逻辑分析+实验事实+科普节目都没法让你放弃1/2的结论,那我真无能为力了:)