三門問題,也稱為蒙提霍爾問題(Monty Hall Problem)。
你在參加一個節目,面前是三扇關閉着的門。其中一扇后面是小汽車,選中它就可贏得汽車,另外兩扇后面各是一只羊。你選擇了其中一扇,但沒有打開它,這時主持人打開了剩下兩扇門中的一扇,后面是一只山羊(這里有個隱含前提:主持人是知道門后的情況的)。主持人問你,要不要換另一扇仍然關閉着的門,還是就要你剛才選中的那扇。
那么問題就是,換另一扇門會增加你贏得汽車的概率么?換與不換的概率各是多少呢?
因為只剩下了兩扇門,其中有一車和一羊,因此答案是換不換概率都是1/2,對么?
也有人堅信不換的概率是1/3,那么換的概率就應該是2/3?無論哪種回答,一定都會有自己的解釋和邏輯。
什么是概率?無非就是某種事件發生的可能性。
如何驗證概率?只有用大量的實驗來統計各種事件發生的分布情況。
放到現實中,想看到這個問題的答案,只能由主持人和觀眾不斷的重復進行游戲。
看看比如說各自100次游戲,不換門會選中多少次,換門又會選中多少次。
這就體現出了代碼的優勢,無需舞台無需觀眾無需主持人,也無需一遍又一遍的重復。
讓我們直接拋開語義和邏輯上的爭論,讓事實來說話。
完全忠實於游戲的規則來實現:
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的結論,那我真無能為力了:)