趣味編程:三門問題


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM