Парадокс Монти-Холла

Есть такая интересная задача - парадокс Монти-Холла.

Суть ее в следующем. Представьте, вы играете в следующую игру: перед вами три ящика, и в одном из них приз. Два остальных пустые. Вам надо угадать ящик с призом. Вы делаете первую попытку и наугад выбираете один ящик из трех, но ящик пока не открывают. Вместо этого ведущий игры берет и открывает один из двух оставшихся ящиков, и тот оказывается пустым. После этого ведущий вам предлагает возможность изменить первоначальный выбор в свете новой информации о пустом ящике.

Естественно, ведущий точно заранее знает где приз и заведомо открывает пустой ящик. Итак, вы изначально выбрали ящик, но потом ведущий открыл один из оставшихся и выяснилось, что он пустой. Перед вами выбор: оставить свой изначальный выбор неизменным или изменить его, выбрав третий ящик (тот, что остался после вашего первого выбора и после открытия ведущим пустого ящика). При какой стратегии вероятность выигрыша выше?

Самое прямолинейное решение, приходящее в голову: смена ящика ничего особенно не даст. Вы выбрали один ящик из трех - вероятность выиграть 13. После открытия одного ящика ведущим их осталось два, поэтому вероятность угадать где приз 5050. Выбор вы уже сделали, и он и так является одним из текущих вариантов. Выходит, что нет особого смысла менять выбор.

Но эта задачка тем и интересна, что при столь тривиальной постановке ее правильное решение не совсем очевидно, хотя с точки зрения теории вероятности тут все прозрачно - теорему Байеса еще никто не отменял.

Правильный ответ - да, надо менять выбор, так как в этом случае вероятность угадать повышается с 1/3 до 2/3 (и даже не 1/2).

В Википедии приведено исчерпывающее объяснение.

Ну а чтобы уж окончательно развеять все сомнения, пришлось провести эксперимент.

montihall.cpp:

#include <iostream>
#include <set>
#include <cstdlib>
#include <cassert>
#include <ctime>

int all_doors[] = { 1, 2, 3 };

bool no_change_strategy() {
  // doors - это множество доступных дверей (1, 2, 3) для выбора игроком.
  std::set<int> doors(all_doors, all_doors + 3);

  // Выбираем истинную дверь (от 1 до 3).
  int real_door = (std::rand() % 3) + 1;

  // Выбираем первый и окончательный выбор игрока (от 1 до 3).
  int primary_choice_door = (std::rand() % 3) + 1;

  return real_door == primary_choice_door;
}

bool change_strategy() {
  // doors - это множество доступных дверей (1, 2, 3) для выбора двери,
  // открываемой ведущим после первого выбора игрока.
  std::set<int> doors(all_doors, all_doors + 3);

  // Выбираем истинную дверь (от 1 до 3).
  int real_door = (std::rand() % 3) + 1;

  // Выбираем первый выбор игрока (от 1 до 3)
  int primary_choice_door = (std::rand() % 3) + 1;

  // Исключаем из множества дверей истинную дверь и выбор игрока.
  doors.erase(real_door);
  doors.erase(primary_choice_door);
  // На всякий пожарный проверим целостность данных.
  assert(doors.size() == 1 || doors.size() == 2);

  // Из оставшихся элементов (их может быть 1 или 2 штуки) выбираем дверь,
  // которую откроет ведущий. reveal_door равно либо 1, либо 2.
  int reveal_door = (std::rand() % doors.size()) + 1;

  // i указывает на первый элемент в множестве (всего в нем 1 или 2 элемента).
  std::set<int>::const_iterator i = doors.begin();
  // Сдвигаем итератор на элемент, номер которого равен reveal_door.
  // Можно было бы написать "if (reveal_door == 2) ++i;", но цикл как-то
  // универсальнее.
  while (--reveal_door) ++i;
  reveal_door = *i;

  // 'doors2' - это множество доступных дверей (1, 2, 3) для игрока,
  // меняющего свой первоначальный выбор.
  std::set<int> doors2(all_doors, all_doors + 3);

  // Исключаем из множества дверей первый выбор игрока и
  // и дверь, открытую ведущим.
  doors2.erase(primary_choice_door);
  doors2.erase(reveal_door);
  // На всякий пожарный проверим целостность данных.
  assert(doors2.size() == 1);

  // В множестве оставшихся дверей будет только одна дверь, так как истинная 
  // дверь точно не равна двери, открытой ведущим, во второй выбор игрока
  // точно отличается от первоначального. Поэтому просто берем из этого 
  // множества первый элемент.
  int second_choice = *doors2.begin();

  return real_door == second_choice;
}

int main() {
  std::srand(std::time(0));
  int guess_on_change = 0;
  int guess_on_not_change = 0;
  int N = 100000;
  for (int i = 0; i < N; ++i) {
    if (change_strategy())
      guess_on_change = guess_on_change + 1;
    if (no_change_strategy())
      guess_on_not_change = guess_on_not_change + 1;
  }
  std::cout << "Вероятность выиграть при смене изначального выбора: "
    << guess_on_change * 1.0 / N << std::endl;
  std::cout << "Вероятность выиграть не меняя изначального выбора: "
    << guess_on_not_change * 1.0 / N << std::endl;
  return 0;
}

Компилируем и запускаем:

cl /EHsc /D_NDEBUG montihall.cpp && montihall

Результат подтверждает теорию:

Вероятность выиграть при смене изначального выбора: 0.67005
Вероятность выиграть не меняя изначального выбора: 0.33347

Лично я провел замечательные несколько часов, вспоминая всю эту тему условных вероятностей. А вы?


Оригинальный пост | Disclaimer

Комментарии