ผมชอบปัญหา Monty Hall Problem มาก เมื่อครั้งแรกที่ได้ยินก็รู้สึกว่ามีอะไรชวนประหลาดพอสมควร เลยคิดว่าจะเอามาเล่าต่อสักหน่อย เริ่มจากการอธิบายก่อนแล้วกันนะครับว่ามันคืออะไร
เริ่มจากมีรายการเกมโชว์ทีวีรายการหนึ่งชื่อว่า Let's Make a Deal ซึ่งมีพิธีกรชื่อ Monty Hall (ก็เลยกลายเป็น Monty Hall Problem นั่นแหละ) ในรายการจะมีประตูให้ 3 บาน โดยที่ 1 ใน 3 ของประตูจะเปิดแล้วเจอรถ ส่วนอีก 2 บานเปิดแล้วจะเป็นแกะ ซึ่งถ้าเปิดแล้วเจอรถก็คงจะได้รถกลับบ้านล่ะมั้ง (ไม่ชัวร์เพราะไม่เคยดูเหมือนกัน แต่รายการมันคงไม่ขี้งกเกินไปมั้ง) เริ่มต้นคือแขกรับเชิญจะได้เลือกประตู 1 บานก่อน แต่พิธีกรจะยังไม่เฉลย แต่จะไปเปิดประตู 1 ใน 2 บานที่แขกรับเชิญไม่ได้เลือก และเปิดให้ดูว่าเป็นแกะ แล้วถามแขกรับเชิญว่าจะเปลี่ยนใจเลือกประตูอีกบานหรือไม่
ตัวรายการก็มีเพียงเท่านี้แหละครับ แต่จะมีตัวละครอีกคนโผล่ออกมาคือผู้หญิงอีกคนที่ชื่อว่ามาริลิน (Marilyn vos Savant) ซึ่งเป็นผู้ได้รับการบันทึกลงกินเนสบุ๊คว่ามี IQ สูงที่สุดในโลก มาริลินเปิดคอลัมภ์ Ask Marilyn อยู่ในนิตยสาร Parade ให้คนถามคำถามเกี่ยวกับคณิตศาสตร์เข้ามาได้ ทีนี้ก็มีคนที่สงสัยจากการดูรายการ Let's Make a Deal ส่งจดหมายไปถามว่าถ้าเกิดสถานการณ์ขึ้นแบบนี้ ควรจะเปลี่ยนใจเลือกประตูอีกบานหรือไม่
มาริลินตอบว่า สมควรเปลี่ยน เพราะถ้าไม่เปลี่ยนโอกาสที่จะเปิดได้รถจะเป็น 1/3 แต่ว่าถ้าเปลี่ยนจะมีโอกาสเปิดเจอรถเป็น 2/3 เลยทีเดียว
แน่นอนว่าฟังแล้วก็ออกจะขัดแย้งกับความรู้สึกของหลายๆ คน การไม่เปลี่ยนประตูโอกาสที่จะเจอรถเป็น 1/3 แบบไม่ต้องสงสัย แต่ในเมื่อหลังจากที่ตัดประตูทิ้งไป 1 บานแล้ว การเลือกเปลี่ยนหรือไม่เปลี่ยนน่าจะเหมือนกับเหลือประตู 2 บาน ซึ่งไม่ว่าเลือกที่จะเปลี่ยนหรือไม่เปลี่ยนโอกาสที่จะได้รถก็น่าจะเป็น 1/2 เท่ากันรึเปล่า?
มีหลายคนที่ออกมาเขียนตอบโต้มาริลิน แล้วก็มีการเขียนอธิบายเพิ่มเติมจากมาริลินเพื่อโต้กลับเช่นกัน แต่ตรงนี้ผมขอข้ามรายละเอียดไปนะครับ ใครที่สนใจลองไปหาอ่านต่อดูว่าเขาอธิบายกันต่อว่ายังไง
แต่ไม่ว่าจะฟังยังไงก็ยังเกิดความสงสัยอยู่ว่าทำไมมันถึงเป็น 1/3 ได้ คราวนี้เลยจะมาลองเขียนโปรแกรมเพื่อ simulate ดูว่าความจริงแล้วเป็นยังไงกันแน่ มาลองเริ่มกันเลยครับ
ผมจะเริ่มจากโปรแกรมจำลองในกรณีที่เราไม่เปลี่ยนใจก่อนนะครับ ซึ่งโปรแกรมก็จะง่อยๆ ประมาณนี้ครับ แค่สุ่มหมายเลขประตูที่มีรถ (1-3) และสุ่มหมายเลขประตูที่แขกรับเชิญเลือก (1-3 เหมือนกัน) จากนั้นถ้าตรงกันเราก็เพิ่มค่าให้กับตัวแปรที่เก็บว่าตอบถูกไปกี่ครั้ง
int car_door = rand() % 3 + 1; int selected_door = rand() % 3 + 1; if (selected_door == car_door) { correct += 1; }
เขียนให้ครบทั้งโปรแกรมจะเป็นแบบนี้
int t, correct = 0, n = 300000; for (t = 0; t < n; t++) { int car_door = rand() % 3 + 1; int selected_door = rand() % 3 + 1; if (selected_door == car_door) correct += 1; } cout << "Correct " << correct << " from " << n << "\n"; cout << correct * 1.0 / n << " %\n";
ตัวโปรแกรมจะวนลูปเพื่อจำลองสถานการณ์นี้เป็นจำนวน 300,000 ครั้ง แล้วดูว่าจำนวนครั้งที่ทายถูกจะเป็นเท่าไหร่ เมื่อรันออกมาก็จะเห็นว่าจาก 300,000 รอบ จะตอบถูกอยู่ที่ประมาณ 100,000 ครั้ง หรือประมาณ 1/3 จริงๆ ซึ่งอันนี้ก็ตามที่คาดการณ์ไว้
Correct 99692 from 300000 0.332307 %
ที่นี้เราลองจำลองเพิ่ม ในกรณีที่ผู้เล่นจะเปลี่ยนใจทุกครั้งหลังจากพิธีกรเปิดประตูแล้ว ด้วยการให้พิธีกรเปิดประตูที่เป็นแกะก่อน จากนั้นผู้เล่นจะเปลี่ยนไปเลือกอีกประตูที่เหลือแทน ในขั้นตอนการจำลองก็จะต้องหาก่อนว่าประตูที่ผู้เล่นไม่ได้เลือกคือประตูใดบ้าง ตามโค้ดด้านล่างนี้
vector<int> remain_doors; for (int d = 1; d <= 3; d++) if (d != selected_door) remain_doors.push_back(d);
ประตูที่ไม่ได้เลือกจะเก็บไว้ที่ remain_doors ซึ่งจะเก็บสองค่าแน่นอน ถ้าผู้เล่นเลือก 2 ก็จะเก็บ [1,3] ถ้าผู้เล่นเลือก 3 ก็จะเก็บ [1,2] หรือถ้าผู้เล่นเลือก 1 ก็จะเก็บ [2,3] หลังจากได้ประตูที่เหลือแล้ว พิธีกรก็จะต้องเปิดประตูทิ้งไป 1 ประตูจาก 2 บานนี้ ถึงตรงนี้โปรแกรมเราจะต้องแบ่งออกเป็น 3 กรณีย่อย คือ
- ประตูที่เหลือทั้งสองไม่มีรถอยู่เลย ดังนั้นพิธีกรก็จะสุ่มเปิดขึ้นมา 1 บาน และผู้เล่นก็จะเปลี่ยนไปเปิดบานทีเหลือ
- ประตูที่เหลือบานแรกเป็นแกะ และบานที่สองเป็นรถ พิธีกรจะต้องเปิดบานที่เป็นแกะ และผู้เล่นจะเปลี่ยนมาเลือกบานที่เหลือซึ่งเป็นรถทันที
- ประตูที่เหลือบานแรกเป็นรถ และบานที่สองเป็นแกะ เช่นเดียวกัน พิธีกรจะเปิดบานที่เป็นแกะ และผู้เล่นก็จะเปลี่ยนมาเปิดบานที่เป็นรถ
ทั้งหมดนี้ก็จะได้โค้ดออกมาหน้าตาประมาณนี้ครับ
if (remain_doors[0] != car_door && remain_doors[1] != car_door) selected_door = remain_doors[rand() % 2]; else if (remain_doors[0] != car_door) selected_door = remain_doors[1]; else selected_door = remain_doors[0];
เขียนแบบเต็มๆ ทั้งหมดเลยก็จะได้เป็น
int t, correct = 0, n = 300000; for (t = 0; t < n; t++) { int car_door = rand() % 3 + 1; int selected_door = rand() % 3 + 1; vector<int> remain_doors; for (int d = 1; d <= 3; d++) if (d != selected_door) remain_doors.push_back(d); if (remain_doors[0] != car_door && remain_doors[1] != car_door) selected_door = remain_doors[rand() % 2]; else if (remain_doors[0] != car_door) selected_door = remain_doors[1]; else selected_door = remain_doors[0]; if (selected_door == car_door) correct += 1; } cout << "Correct " << correct << " from " << n << "\n"; cout << correct * 1.0 / n << " %\n";
และเมื่อรันออกมาผมลัพท์ก็เป็นดังนี้
Correct 199639 from 300000 0.665463 %
2/3 จริงๆ ด้วย!!! แต่จริงๆ ตอนที่เขียนโปรแกรมสำหรับจำลองออกมาก็เริ่มจะไม่แปลกใจแล้วล่ะครับ เพราะเงื่อนไขที่พิธีกรจะเลือกประตูที่จะเปิดมีอยู่ 3 แบบ ซึ่ง 2/3 นั้นถ้าผู้เล่นเปลี่ยนก็จะได้รถกลับบ้านไปแน่ๆ
ปัญหานี้อ่านกี่ครั้งก็ยังรู้สึกประหลาดอยู่เหมือนเดิม แต่ถ้าค่อยๆ ลองไล่สถานการณ์ดูแล้วก็จะพบว่าโอกาสที่จะได้รถมันเพิ่มขึ้นจริงๆ นะ
Reference
https://en.wikipedia.org/wiki/Monty_Hall_problem - ขอขอบคุณ Wikipedia สำหรับข้อมูลเชิงรายละเอียด เช่น ใครชื่ออะไรและออกรายการอะไร ถึงจะบอกว่าใช้อ้างอิงไม่ได้ แต่กรณีนี้ก็น่าเชื่อถือพอมั้งครับ