該問題由丹麥計算機科學家Peter Bro Miltersen於2003年首次提出。
問題描述
在監獄中有100名囚犯,被編號爲1-100號。典獄長決定給囚犯們一次特赦的機會,條件是通過一項挑戰。在一個房間中放着一個有100個抽屜的櫥櫃,裏面隨機放着與囚犯編號對應的1-100的號碼牌。挑戰開始後,每個囚犯依次進入該房間,打開不超過半數的抽屜,並從中找到與自己對應的號碼則爲成功,每名囚犯出去時該櫥櫃恢復原樣。從第一名囚犯進入直至最後一名囚犯出來期間不允許有任何交流,任何一名囚犯挑戰失敗都會導致所有囚犯死亡,只有全部成功才能夠特赦該100名囚犯。如果囚犯們都隨機打開50個抽屜,他們的生存機率微乎其微。所以囚犯們需要找到一個最佳策略,來提高生存率。
最佳策略
- 每個囚犯首先打開與自己號碼對應的抽屜;
- 如果該抽屜裏的號碼牌是此囚犯的號碼,則該囚犯挑戰成功;
- 否則該抽屜中存放的是另一個囚犯對應的號碼牌,接着用該號碼牌對應的抽屜;
- 每名囚犯重複2和3的步驟,直到找到自己的號碼牌或者打開了50個抽屜爲止。
舉例
使用8個囚犯和抽屜來進行演示,每個囚犯最多可以打開4個抽屜。典獄長將抽屜中的號碼牌如下襬放。
抽屜號碼 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
號碼牌 | 7 | 4 | 6 | 8 | 1 | 3 | 5 | 2 |
囚犯的行爲如下:
- 囚犯1首先打開抽屜1並找到數字7。然後他打開抽屜7並找到數字5。然後打開抽屜5並在其中成功找到自己的數字。
- 囚犯2依次打開抽屜2、4和8,並在最後一個抽屜中找到了自己的數字2。
- 囚犯3打開抽屜3和6,在其中找到自己的號碼牌。
- 囚犯4打開抽屜4、8和2,在2號抽屜中找到自己的編號。請注意,這與囚犯2遇到的週期相同,但他不知道。
- 5至8號囚犯也將以相似的方式找到自己的號碼。
這種情況下,囚犯們都能夠找到自己的數字,但並非所有情況都如此幸運。例如,將抽屜5和8的號碼牌互換,將導致1號囚犯找不到自己的號碼牌。
代碼實現
代碼實現主要比較以下兩種情況下的生存概率:
- 用數千個實例模擬囚犯隨即打開抽屜
- 用數千個實例模擬囚犯使用最佳策略打開抽屜
C
#include<stdbool.h>
#include<stdlib.h>
#include<stdio.h>
#include<time.h>
#define LIBERTY false
#define DEATH true
typedef struct{
int id;
int cardNum;
bool hasBeenOpened;
}drawer;
typedef struct{
int id;
bool foundCard;
}prisoner;
drawer *drawerSet;
prisoner *prisonerGang;
void initialize(int prisoners){
int i,j,card;
bool unique;
drawerSet = (drawer*)malloc(prisoners * sizeof(drawer));
prisonerGang = (prisoner*)malloc(prisoners * sizeof(prisoner));
for(i=0;i<prisoners;i++){
prisonerGang[i] = (prisoner){.id = i+1, .foundCard = false};
card = rand()%prisoners + 1;
if(i==0)
drawerSet[i] = (drawer){.id = i+1, .cardNum = card, .hasBeenOpened = false};
else{
unique = false;
while(unique==false){
for(j=0;j<i;j++){
if(drawerSet[j].cardNum == card){
card = rand()%prisoners + 1;
break;
}
}
if(j==i){
unique = true;
}
}
drawerSet[i] = (drawer){.id = i+1, .cardNum = card, .hasBeenOpened = false};
}
}
}
void closeAllDrawers(int prisoners){
int i;
for(i=0;i<prisoners;i++)
drawerSet[i].hasBeenOpened = false;
}
bool libertyOrDeathAtRandom(int prisoners,int chances){
int i,j,chosenDrawer;
for(i=0;i<prisoners;i++){
for(j=0;j<chances;j++){
do{
chosenDrawer = rand()%prisoners;
}while(drawerSet[chosenDrawer].hasBeenOpened==true);
if(drawerSet[chosenDrawer].cardNum == prisonerGang[i].id){
prisonerGang[i].foundCard = true;
break;
}
drawerSet[chosenDrawer].hasBeenOpened = true;
}
closeAllDrawers(prisoners);
if(prisonerGang[i].foundCard == false)
return DEATH;
}
return LIBERTY;
}
bool libertyOrDeathPlanned(int prisoners,int chances){
int i,j,chosenDrawer;
for(i=0;i<prisoners;i++){
chosenDrawer = rand()%prisoners;
for(j=1;j<chances;j++){
if(drawerSet[chosenDrawer].cardNum == prisonerGang[i].id){
prisonerGang[i].foundCard = true;
break;
}
if(chosenDrawer+1 == drawerSet[chosenDrawer].id){
do{
chosenDrawer = rand()%prisoners;
}while(drawerSet[chosenDrawer].hasBeenOpened==true);
}
else{
chosenDrawer = drawerSet[chosenDrawer].cardNum - 1;
}
drawerSet[chosenDrawer].hasBeenOpened = true;
}
closeAllDrawers(prisoners);
if(prisonerGang[i].foundCard == false)
return DEATH;
}
return LIBERTY;
}
int main(int argc,char** argv)
{
int prisoners, chances;
unsigned long long int trials,i,count = 0;
char* end;
if(argc!=4)
return printf("Usage : %s <Number of prisoners> <Number of chances> <Number of trials>",argv[0]);
prisoners = atoi(argv[1]);
chances = atoi(argv[2]);
trials = strtoull(argv[3],&end,10);
srand(time(NULL));
printf("Running random trials...");
for(i=0;i<trials;i+=1L){
initialize(prisoners);
count += libertyOrDeathAtRandom(prisoners,chances)==DEATH?0:1;
}
printf("\n\nGames Played : %llu\nGames Won : %llu\nChances : %lf % \n\n",trials,count,(100.0*count)/trials);
count = 0;
printf("Running strategic trials...");
for(i=0;i<trials;i+=1L){
initialize(prisoners);
count += libertyOrDeathPlanned(prisoners,chances)==DEATH?0:1;
}
printf("\n\nGames Played : %llu\nGames Won : %llu\nChances : %lf % \n\n",trials,count,(100.0*count)/trials);
return 0;
}
測試:
C:\My Projects\networks>a 100 50 100000
Running random trials...
Games Played : 100000
Games Won : 0
Chances : 0.000000%
Running strategic trials...
Games Played : 100000
Games Won : 0
Chances : 0.000000
C:\My Projects\networks>a 100 50 1000000
Running random trials...
Games Played : 1000000
Games Won : 0
Chances : 0.000000
Running strategic trials...
Games Played : 1000000
Games Won : 0
Chances : 0.000000
C#
using System;
using System.Linq;
namespace Prisoners {
class Program {
static bool PlayOptimal() {
var secrets = Enumerable.Range(0, 100).OrderBy(a => Guid.NewGuid()).ToList();
for (int p = 0; p < 100; p++) {
bool success = false;
var choice = p;
for (int i = 0; i < 50; i++) {
if (secrets[choice] == p) {
success = true;
break;
}
choice = secrets[choice];
}
if (!success) {
return false;
}
}
return true;
}
static bool PlayRandom() {
var secrets = Enumerable.Range(0, 100).OrderBy(a => Guid.NewGuid()).ToList();
for (int p = 0; p < 100; p++) {
var choices = Enumerable.Range(0, 100).OrderBy(a => Guid.NewGuid()).ToList();
bool success = false;
for (int i = 0; i < 50; i++) {
if (choices[i] == p) {
success = true;
break;
}
}
if (!success) {
return false;
}
}
return true;
}
static double Exec(uint n, Func<bool> play) {
uint success = 0;
for (uint i = 0; i < n; i++) {
if (play()) {
success++;
}
}
return 100.0 * success / n;
}
static void Main() {
const uint N = 1_000_000;
Console.WriteLine("# of executions: {0}", N);
Console.WriteLine("Optimal play success rate: {0:0.00000000000}%", Exec(N, PlayOptimal));
Console.WriteLine(" Random play success rate: {0:0.00000000000}%", Exec(N, PlayRandom));
}
}
}
輸出:
# of executions: 1000000
Optimal play success rate: 31.21310000000%
Random play success rate: 0.00000000000%
C++
#include <iostream> //for output
#include <algorithm> //for shuffle
#include <stdlib.h> //for rand()
using namespace std;
int* setDrawers() {
int drawers[100];
for (int i = 0; i < 100; i++) {
drawers[i] = i;
}
random_shuffle(&drawers[0], &drawers[99]);
return drawers;
}
bool playRandom()
{
int* drawers = setDrawers();
bool openedDrawers[100] = { 0 };
for (int prisonerNum = 0; prisonerNum < 100; prisonerNum++) { //loops through prisoners numbered 0 through 99
bool prisonerSuccess = false;
for (int i = 0; i < 50; i++) { //loops through 50 draws for each prisoner
int drawerNum;
while (true) {
drawerNum = rand() % 100;
if (!openedDrawers[drawerNum]) {
openedDrawers[drawerNum] = true;
cout << endl;
break;
}
}
if (*(drawers + drawerNum) == prisonerNum) {
prisonerSuccess = true;
break;
}
}
if (!prisonerSuccess)
return false;
}
return true;
}
bool playOptimal()
{
int* drawers = setDrawers();
for (int prisonerNum = 0; prisonerNum < 100; prisonerNum++) {
bool prisonerSuccess = false;
int checkDrawerNum = prisonerNum;
for (int i = 0; i < 50; i++) {
if (*(drawers + checkDrawerNum) == prisonerNum) {
prisonerSuccess = true;
break;
}
else
checkDrawerNum = *(drawers + checkDrawerNum);
}
if (!prisonerSuccess)
return false;
}
return true;
}
double simulate(string strategy)
{
int numberOfSuccesses = 0;
for (int i = 0; i <= 10000; i++) {
if ((strategy == "random" && playRandom()) || (strategy == "optimal" && playOptimal())) //will run playRandom or playOptimal but not both becuase of short-circuit evaluation
numberOfSuccesses++;
}
return numberOfSuccesses / 100.0;
}
int main()
{
cout << "Random Strategy: " << simulate("random") << "%" << endl;
cout << "Optimal Strategy: " << simulate("optimal") << "%" << endl;
system("PAUSE");
return 0;
}
輸出:
Random Strategy: 0%
Optimal Strategy: 31.51%
Go
package main
import (
"fmt"
"math/rand"
"time"
)
// Uses 0-based numbering rather than 1-based numbering throughout.
func doTrials(trials, np int, strategy string) {
pardoned := 0
trial:
for t := 0; t < trials; t++ {
var drawers [100]int
for i := 0; i < 100; i++ {
drawers[i] = i
}
rand.Shuffle(100, func(i, j int) {
drawers[i], drawers[j] = drawers[j], drawers[i]
})
prisoner:
for p := 0; p < np; p++ {
if strategy == "optimal" {
prev := p
for d := 0; d < 50; d++ {
this := drawers[prev]
if this == p {
continue prisoner
}
prev = this
}
} else {
// Assumes a prisoner remembers previous drawers (s)he opened
// and chooses at random from the others.
var opened [100]bool
for d := 0; d < 50; d++ {
var n int
for {
n = rand.Intn(100)
if !opened[n] {
opened[n] = true
break
}
}
if drawers[n] == p {
continue prisoner
}
}
}
continue trial
}
pardoned++
}
rf := float64(pardoned) / float64(trials) * 100
fmt.Printf(" strategy = %-7s pardoned = %-6d relative frequency = %5.2f%%\n\n", strategy, pardoned, rf)
}
func main() {
rand.Seed(time.Now().UnixNano())
const trials = 100_000
for _, np := range []int{10, 100} {
fmt.Printf("Results from %d trials with %d prisoners:\n\n", trials, np)
for _, strategy := range [2]string{"random", "optimal"} {
doTrials(trials, np, strategy)
}
}
}
輸出:
Results from 100000 trials with 10 prisoners:
strategy = random pardoned = 99 relative frequency = 0.10%
strategy = optimal pardoned = 31205 relative frequency = 31.20%
Results from 100000 trials with 100 prisoners:
strategy = random pardoned = 0 relative frequency = 0.00%
strategy = optimal pardoned = 31154 relative frequency = 31.15%
Java
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Main {
private static boolean playOptimal(int n) {
List<Integer> secretList = IntStream.range(0, n).boxed().collect(Collectors.toList());
Collections.shuffle(secretList);
prisoner:
for (int i = 0; i < secretList.size(); ++i) {
int prev = i;
for (int j = 0; j < secretList.size() / 2; ++j) {
if (secretList.get(prev) == i) {
continue prisoner;
}
prev = secretList.get(prev);
}
return false;
}
return true;
}
private static boolean playRandom(int n) {
List<Integer> secretList = IntStream.range(0, n).boxed().collect(Collectors.toList());
Collections.shuffle(secretList);
prisoner:
for (Integer i : secretList) {
List<Integer> trialList = IntStream.range(0, n).boxed().collect(Collectors.toList());
Collections.shuffle(trialList);
for (int j = 0; j < trialList.size() / 2; ++j) {
if (Objects.equals(trialList.get(j), i)) {
continue prisoner;
}
}
return false;
}
return true;
}
private static double exec(int n, int p, Function<Integer, Boolean> play) {
int succ = 0;
for (int i = 0; i < n; ++i) {
if (play.apply(p)) {
succ++;
}
}
return (succ * 100.0) / n;
}
public static void main(String[] args) {
final int n = 100_000;
final int p = 100;
System.out.printf("# of executions: %d\n", n);
System.out.printf("Optimal play success rate: %f%%\n", exec(n, p, Main::playOptimal));
System.out.printf("Random play success rate: %f%%\n", exec(n, p, Main::playRandom));
}
}
輸出:
# of executions: 100000
Optimal play success rate: 31.343000%
Random play success rate: 0.000000%
JavaScript
const _ = require('lodash');
const numPlays = 100000;
const setupSecrets = () => {
// setup the drawers with random cards
let secrets = [];
for (let i = 0; i < 100; i++) {
secrets.push(i);
}
return _.shuffle(secrets);
}
const playOptimal = () => {
let secrets = setupSecrets();
// Iterate once per prisoner
loop1:
for (let p = 0; p < 100; p++) {
// whether the prisoner succeedss
let success = false;
// the drawer number the prisoner chose
let choice = p;
// The prisoner can choose up to 50 cards
loop2:
for (let i = 0; i < 50; i++) {
// if the card in the drawer that the prisoner chose is his card
if (secrets[choice] === p){
success = true;
break loop2;
}
// the next drawer the prisoner chooses will be the number of the card he has.
choice = secrets[choice];
} // each prisoner gets 50 chances
if (!success) return false;
} // iterate for each prisoner
return true;
}
const playRandom = () => {
let secrets = setupSecrets();
// iterate for each prisoner
for (let p = 0; p < 100; p++) {
let choices = setupSecrets();
let success = false;
for (let i = 0; i < 50; i++) {
if (choices[i] === p) {
success = true;
break;
}
}
if (!success) return false;
}
return true;
}
const execOptimal = () => {
let success = 0;
for (let i = 0; i < numPlays; i++) {
if (playOptimal()) success++;
}
return 100.0 * success / 100000;
}
const execRandom = () => {
let success = 0;
for (let i = 0; i < numPlays; i++) {
if (playRandom()) success++;
}
return 100.0 * success / 100000;
}
console.log("# of executions: " + numPlays);
console.log("Optimal Play Success Rate: " + execOptimal());
console.log("Random Play Success Rate: " + execRandom());
Python
import random
def play_random(n):
# using 0-99 instead of ranges 1-100
pardoned = 0
in_drawer = list(range(100))
sampler = list(range(100))
for _round in range(n):
random.shuffle(in_drawer)
found = False
for prisoner in range(100):
found = False
for reveal in random.sample(sampler, 50):
card = in_drawer[reveal]
if card == prisoner:
found = True
break
if not found:
break
if found:
pardoned += 1
return pardoned / n * 100 # %
def play_optimal(n):
# using 0-99 instead of ranges 1-100
pardoned = 0
in_drawer = list(range(100))
for _round in range(n):
random.shuffle(in_drawer)
for prisoner in range(100):
reveal = prisoner
found = False
for go in range(50):
card = in_drawer[reveal]
if card == prisoner:
found = True
break
reveal = card
if not found:
break
if found:
pardoned += 1
return pardoned / n * 100 # %
if __name__ == '__main__':
n = 100_000
print(" Simulation count:", n)
print(f" Random play wins: {play_random(n):4.1f}% of simulations")
print(f"Optimal play wins: {play_optimal(n):4.1f}% of simulations")
輸出:
Simulation count: 100000
Random play wins: 0.0% of simulations
Optimal play wins: 31.1% of simulations