三門問題,也稱爲蒙提霍爾問題(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的結論,那我真無能爲力了:)