2020-12-14

泡鍋發表於2020-12-14

三門問題(Monty Hall problem)

昨天在刷douyin時,恰巧刷到了一條關於數學概率的小視訊。點贊人數很多,評論人數也很多,重點是視訊的內容談到的三門問題確實非常的吸引人去思考(反正我有被吸引到,謝謝)。。

視訊很短,結尾處就把“答案”說出來了。

當然,以我這腦瓜子,拿著手機愣是想了半個多小時,依然沒弄明白為啥我的想法與公佈的答案不符,一度讓我懷疑我的智商。翻看了一下評論區,大家的想法和意見也都有分歧,有和我想的一樣的,也有和公佈的答案一樣的,還很熱心的把原因講解了一遍,,,嗯,我還是沒搞懂。

 

進入正題

首先看一下什麼是“三門問題”。接下來是度娘登場時間~~~

三門問題(Monty Hall problem)亦稱為蒙提霍爾問題、蒙特霍問題或蒙提霍爾悖論,大致出自美國的電視遊戲節目Let's Make a Deal。問題名字來自該節目的主持人蒙提·霍爾(Monty Hall)。參賽者會看見三扇關閉了的門,其中一扇的後面有一輛汽車,選中後面有車的那扇門可贏得該汽車,另外兩扇門後面則各藏有一隻山羊。當參賽者選定了一扇門,但未去開啟它的時候,節目主持人開啟剩下兩扇門的其中一扇,露出其中一隻山羊。主持人其後會問參賽者要不要換另一扇仍然關上的門。問題是:換另一扇門會否增加參賽者贏得汽車的機率。如果嚴格按照上述的條件,那麼答案是會。不換門的話,贏得汽車的機率是1/3。換門的話,贏得汽車的機率是2/3。

以上的說法可能不是很嚴謹哈,下面給出更為嚴謹的陳述:

  • 現在有三扇門,只有一扇門有汽車,其餘兩扇門的都是山羊。

  • 汽車事前是等可能地被放置於三扇門的其中一扇後面。

  • 參賽者在三扇門中挑選一扇。他在挑選前並不知道任意一扇門後面是什麼。

  • 主持人知道每扇門後面有什麼。

  • 如果參賽者挑了一扇有山羊的門,主持人必須挑另一扇有山羊的門。

  • 如果參賽者挑了一扇有汽車的門,主持人等可能地在另外兩扇有山羊的門中挑一扇門。

  • 參賽者會被問是否保持他的原來選擇,還是轉而選擇剩下的那一扇門。

嗯,陳述看完,就要講講為啥換門後中獎的概率要高了。(解法很多,這裡只列舉一種比較通俗易懂的解題思路,依然來自度娘,,,感謝度孃的大力支援,謝謝。。)

          當參賽者轉向另一扇門而不是維持原先的選擇時,贏得汽車的機會將會加倍。

          有三種可能的情況,全部都有相等的可能性(1/3):

          1、參賽者挑山羊一號,主持人挑山羊二號。轉換將贏得汽車。

          2、參賽者挑山羊二號,主持人挑山羊一號。轉換將贏得汽車。

          3、“參賽者挑汽車,主持人挑羊一號。轉換將失敗”,和“參賽者挑汽車,主持人挑羊二號。轉換將失敗。”此情況的可能性為:1/3 * 1/2 + 1/3 * 1/2 = 1/3。

 

實踐部分

既然我無法用腦瓜子想透它,就要用行動證明它!

我預想的證明方法就兩種:

方法一:用撲克牌

        首先用兩張一樣數字的撲克代表山羊,一張不一樣數字的撲克代表汽車,然後叫上你的小夥伴,愉(枯)快(燥)的玩遊戲!你的小夥伴扮演主持人,你就是玩家,根據規則開始不斷地猜猜猜、換換換、不換不換我不換,同時記錄猜中的次數、不中的次數與總次數,最後再計算換與不換能猜中汽車的概率。

這個方法確實簡單,但是也確實耗時。而且我們知道,概率這東西,嘗試的次數越多,才越接近真實情況。玩個十萬次估計能把人玩廢,哈哈哈。

方法二:用程式碼模擬

上程式碼~~~(程式碼很簡單,學過java的都知道)

import java.util.Random;
import java.util.Scanner;

/**
 * @Description 三門問題模擬
 * @Author HeHuanxing
 * @Date 2020/12/13 22:42
 * @UpdateUser
 * @Version 1.0
 */
public class ThreeQuestionsTest {

    // 測試的次數(即競猜的總次數)
    private static float numberOfGuesses = -1;
    // 玩家選擇的門編號
    private static int selectedNumber = -1;
    // 主持人開啟背後有山羊的門編號
    private static int doorOpenedIndex = -1;
    // 猜中的總次數
    private static float numberOfAwards = 0L;
    // 未猜中的總次數
    private static float noAwards = 0L;
    // 玩家是否更換所選擇的門(預設不更改)
    private static String isChangeChoice = "N";
    // 使用長度為3的陣列表示三個門,初始值為-1,表示不代表任何東西;使用1表示汽車,2表示羊
    private static int[] doors = new int[]{-1, -1, -1};
    // 使用Random物件獲取隨機數,模擬隨機分配汽車和山羊
    private static Random randomForAssigned = new Random();
    // 同樣使用Random物件獲取隨機數,模擬隨機選擇一扇門
    private static Random randomForSelection = new Random();

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.println("請輸入想要測試的次數:");
        numberOfGuesses = input.nextFloat();
        System.out.println("是否更換所選擇的門?(Y/N)");
        isChangeChoice = input.next();
        System.out.print("輸入完成,總的競猜次數為" + numberOfGuesses + "次,並且");
        if (isChangeChoice.equalsIgnoreCase("N")) {
            System.out.println("當主持人詢問是否更換門時,玩家選擇不更換");
        } else {
            System.out.println("當主持人詢問是否更換門時,玩家選擇更換");
        }

        startTheGuessingGame();
    }

    private static void startTheGuessingGame() {
        for (int num = 1; num <= numberOfGuesses; num++) {
            // 每進行一次選擇,均需要初始化一次各個引數,模擬這是一場新的競猜環節
            init();

            // 隨機在三個門後分配汽車和山羊
            randomlyAssigned();

            // 隨機選擇任意一扇門
            randomSelection();

            // 主持人開啟背後有山羊的門,並根據玩家是否願意換門來進行交換門編號
            askAndDetermineTheFinalChoice();

            // 累計猜中的次數與未猜中的次數
            if (doors[selectedNumber] == 1) {
                numberOfAwards++;
            } else {
                noAwards++;
            }
        }
        System.out.println("猜中汽車的概率是:" + numberOfAwards/numberOfGuesses);
        System.out.println("猜不中汽車的概率是:" + noAwards/numberOfGuesses);
    }

    /**
     * 將各個引數初始化
     */
    private static void init() {
        selectedNumber = -1;
        doorOpenedIndex = -1;
        doors[0] = -1;
        doors[1] = -1;
        doors[2] = -1;
    }

    /**
     * 隨機在三個門後分配汽車和山羊
     * 編號說明:使用1表示汽車,使用2表示山羊
     */
    private static void randomlyAssigned() {
        int carIndex = randomForAssigned.nextInt(3);
        doors[carIndex] = 1;
        for (int index = 0; index < doors.length; index++) {
            if (index != carIndex) {
                doors[index] = 2;
            }
        }
    }

    /**
     * 隨機選擇任意一扇門
     */
    private static void randomSelection() {
        selectedNumber = randomForSelection.nextInt(3);
    }

    /**
     * 主持人開啟背後有山羊的門,並根據玩家的換門意願來進行下一步操作
     * 需要明確的是:主持人一定知道門後是汽車還是山羊,開啟的一定是山羊的門
     */
    private static void askAndDetermineTheFinalChoice() {
        // 確定被主持人開啟的門編號
        for (int index = 0; index < doors.length; index++) {
            if (selectedNumber != index && doors[index] == 2) {
                doorOpenedIndex = index;
                break;
            }
        }

        if (isChangeChoice.equalsIgnoreCase("Y")) {
            // 若玩家願意更換前面選中的那一扇門,則玩家交換後選中的即是另一扇沒開啟的門
            for (int index = 0; index < doors.length; index++) {
                if (selectedNumber != index && doorOpenedIndex != index) {
                    selectedNumber = index;
                    break;
                }
            }
        } else {
            // 玩家不願意更換前面選中的那一扇門,則保持原狀
            // do nothing
        }
    }
}

以下是測試結果:

更換門的測試:

請輸入想要測試的次數:
1000000
是否更換所選擇的門?(Y/N)
Y
輸入完成,總的競猜次數為1000000.0次,並且當主持人詢問是否更換門時,玩家選擇更換
猜中汽車的概率是:0.666836
猜不中汽車的概率是:0.333164

不更換門的測試:

請輸入想要測試的次數:
5000000
是否更換所選擇的門?(Y/N)
N
輸入完成,總的競猜次數為5000000.0次,並且當主持人詢問是否更換門時,玩家選擇不更換
猜中汽車的概率是:0.3335684
猜不中汽車的概率是:0.6664316

不論以撲克牌的方式方式驗證,還是使用程式碼去測試,確實換門比不換門,猜中汽車的概率要高。不換門則僅有1/3概率,換了之後,有2/3概率把車開回家!

 

小尾巴~~~~~

部落格賬號申請了這麼久,卻一直沒寫過文章,這個就當做是一個開頭吧。祝自己也祝所有人都能前程似錦 [手動加笑臉]