三门问题(Monty Hall problem)亦称为蒙提霍尔问题、蒙特霍问题或蒙提霍尔悖论,大致出自美国的电视游戏节目Let's Make a Deal。问题名字来自该节目的主持人蒙提·霍尔(Monty Hall)。
参赛者会看见三扇关闭了的门,其中一扇的后面有一辆汽车,选中后面有车的那扇门可赢得该汽车,另外两扇门后面则各藏有一只山羊。
当参赛者选定了一扇门,但未去开启它的时候,节目主持人开启剩下两扇门的其中一扇,露出其中一只山羊。主持人其后会问参赛者要不要换另一扇仍然关上的门。
问题是:换另一扇门是否会增加参赛者赢得汽车的机率?如果严格按照上述的条件,那么答案是会。不换门的话,赢得汽车的几率是1/3。换门的话,赢得汽车的几率是2/3。
听着确实是挺反直觉的,但是自己写代码测试确实也是这样的。流言终结者也有一起视频做了证据。
https://www.ixigua.com/7063739968274825735?id=7137582632111604231
解释1:
总共有三种可能的情况,全部都有相等的可能性(1/3):
参赛者挑山羊一号(1/3),主持人挑山羊二号。转换将赢得汽车。
参赛者挑山羊二号(1/3),主持人挑山羊一号。转换将赢得汽车。
参赛者挑汽车(1/3),主持人挑羊一号。转换将失败。
每种情况出现的概率都为1/3,所以切换策略最终赢得汽车的概率为2/3.
还有一种思路,解释2:
第一次选的空门(概率2/3),之后主持人开另一个空门,换门,得到汽车。
第一次选的汽车(概率1/3),之后主持人开一个空门,换门,失败。
import Foundation
// 测试总次数
let count = 100000
func userPlayGame(strategy: Int) {
var result: [(Int,Bool)] = []
for i in 0 ..< count {
let game = DoorGame()
game.userStrategy = strategy
let oneGameResult = game.startPlay()
let item = (i,oneGameResult)
result.append(item)
}
let winArray = result.filter { item in
return item.1
}
let rate = Double(winArray.count) / Double(count)
var str = ""
if (strategy == 0) {
str = "原选择"
} else if (strategy == 1) {
str = "切换"
} else if (strategy == 2){
str = "随机切换"
}
print("用户游玩\(count)次,使用\(str)策略,\(winArray.count)次胜利,胜率:\(rate)")
// print("原始数据",result)
}
userPlayGame(strategy: 0)
userPlayGame(strategy: 1)
userPlayGame(strategy: 2)
class DoorGame {
// 用户策略
// 0: 不改变选择
// 1: 改变选择
// 2: 50%改策略,50%不改
var userStrategy = 0
// 只是为了方便解释值的含义
enum Strategy: Int {
case notChange = 0 // 必定坚持原选择
case change = 1 // 必定改变原选择
case randomChange = 2 // 50%改变选择, 50%坚持原选择
}
enum DoorResult: Int {
case sheepNotKnow = 0 // 门后是羊
case car = 1 // 门后是车
case sheepKnow = 2 // 主持人打开的门,必定是羊
}
func startPlay() -> Bool {
// 生成3道门的数据
var doorArray = self.buildRandomData()
// 用户第一次选择
var userSelect = self.userSelect()
// 主持人排除一个错误答案, 剩余的结果
doorArray = self.removeOneSheep(array: doorArray, userSelect: userSelect)
// 根据用户策略决定是否转换
if (self.userStrategy == 1) {
userSelect = self.userChangeDoor(array: doorArray, userSelect: userSelect)
} else if (self.userStrategy == 2) {
let random = Int.random(in: 0 ..< 1000)
if random % 2 == 0 {
userSelect = self.userChangeDoor(array: doorArray, userSelect: userSelect)
}
}
// 计算用户最终是否赢得车
let result = self.showResult(array: doorArray, userSelect: userSelect)
return result
}
/// 生成随机数据
func buildRandomData() -> [Int] {
var array = [0,0,0]
let carIndex = Int.random(in: 0...2)
array[carIndex] = 1
return array
}
// 用户进行选择
func userSelect() -> Int {
let userSelect = Int.random(in: 0...2)
return userSelect
}
// 主持人排除一个错误答案
func removeOneSheep(array: [Int], userSelect: Int) -> [Int] {
var result = array
for (i,value) in array.enumerated() {
if i == userSelect {
continue
} else {
// 找到第一个是羊的门,变更成已知羊状态
if value == 0 {
result[i] = 2
break
}
}
}
return result
}
// 用户改变自己的选择
func userChangeDoor(array: [Int], userSelect: Int) -> Int {
var result = -1
// [2,1,0] 用户当前选中为1
for (i,value) in array.enumerated() {
if i == userSelect {
continue
} else {
// 找到第一个是羊的门,移除这条数据
if value == 2 {
continue
} else {
result = i
}
}
}
assert(result != -1, "出错了")
return result
}
/// 展示结果
/// - Parameters:
/// - Returns: true: 用户赢得车; false: 用户失败
func showResult(array: [Int], userSelect: Int) -> Bool {
let value = array[userSelect]
if value == 1 {
return true
}
return false
}
}