该问题由丹麦计算机科学家Peter Bro Miltersen于2003年首次提出。

问题描述

在监狱中有100名囚犯,被编号为1-100号。典狱长决定给囚犯们一次特赦的机会,条件是通过一项挑战。在一个房间中放着一个有100个抽屉的橱柜,里面随机放着与囚犯编号对应的1-100的号码牌。挑战开始后,每个囚犯依次进入该房间,打开不超过半数的抽屉,并从中找到与自己对应的号码则为成功,每名囚犯出去时该橱柜恢复原样。从第一名囚犯进入直至最后一名囚犯出来期间不允许有任何交流,任何一名囚犯挑战失败都会导致所有囚犯死亡,只有全部成功才能够特赦该100名囚犯。如果囚犯们都随机打开50个抽屉,他们的生存几率微乎其微。所以囚犯们需要找到一个最佳策略,来提高生存率。

最佳策略

  1. 每个囚犯首先打开与自己号码对应的抽屉;
  2. 如果该抽屉里的号码牌是此囚犯的号码,则该囚犯挑战成功;
  3. 否则该抽屉中存放的是另一个囚犯对应的号码牌,接着用该号码牌对应的抽屉;
  4. 每名囚犯重复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 truetypedef 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.000000C:\My Projects\networks>a 100 50 1000000
Running random trials...Games Played : 1000000
Games Won : 0
Chances : 0.000000Running 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 99bool 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;}elsecheckDrawerNum = *(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 evaluationnumberOfSuccesses++;}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 mainimport ("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]intfor 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 := pfor 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]boolfor d := 0; d < 50; d++ {var n intfor {n = rand.Intn(100)if !opened[n] {opened[n] = truebreak}}if drawers[n] == p {continue prisoner}}}continue trial}pardoned++}rf := float64(pardoned) / float64(trials) * 100fmt.Printf("  strategy = %-7s  pardoned = %-6d relative frequency = %5.2f%%\n\n", strategy, pardoned, rf)
}func main() {rand.Seed(time.Now().UnixNano())const trials = 100_000for _, 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 cardslet secrets = [];for (let i = 0; i < 100; i++) {secrets.push(i);}return _.shuffle(secrets);
}const playOptimal = () => {let secrets = setupSecrets();// Iterate once per prisonerloop1:for (let p = 0; p < 100; p++) {// whether the prisoner succeedsslet success = false;// the drawer number the prisoner choselet choice = p;// The prisoner can choose up to 50 cardsloop2:for (let i = 0; i < 50; i++) {// if the card in the drawer that the prisoner chose is his cardif (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 chancesif (!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 randomdef play_random(n):# using 0-99 instead of ranges 1-100pardoned = 0in_drawer = list(range(100))sampler = list(range(100))for _round in range(n):random.shuffle(in_drawer)found = Falsefor prisoner in range(100):found = Falsefor reveal in random.sample(sampler, 50):card = in_drawer[reveal]if card == prisoner:found = Truebreakif not found:breakif found:pardoned += 1return pardoned / n * 100   # %def play_optimal(n):# using 0-99 instead of ranges 1-100pardoned = 0in_drawer = list(range(100))for _round in range(n):random.shuffle(in_drawer)for prisoner in range(100):reveal = prisonerfound = Falsefor go in range(50):card = in_drawer[reveal]if card == prisoner:found = Truebreakreveal = cardif not found:breakif found:pardoned += 1return pardoned / n * 100   # %if __name__ == '__main__':n = 100_000print(" 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: 100000Random play wins:  0.0% of simulations
Optimal play wins: 31.1% of simulations

百囚徒问题(100 prisoners problem)相关推荐

  1. 假设有两名囚徒a和b python_囚徒问题(100 prisoners problem)的python验证

    密码学课上老师介绍了这样一个问题,囚徒问题(100 prisoners problem):一百个囚徒被关在牢房里,典狱长给他们最后一次机会,100人依次进入一个有100个抽屉的牢房,每个抽屉置乱放入1 ...

  2. 百囚徒问题(100 prisoners problem)最佳策略Python代码实现(带详细注释)

    代码: # @Time : 2022/8/9 11:00 # @Author : HJL # 100 prisoners problem from random import shuffletimes ...

  3. 百囚犯问题(100 prisoners problem)

    百囚犯问题(100 prisoners problem) 文章目录 百囚犯问题(100 prisoners problem) 问题描述 策略选择 拓展到2n个囚犯 问题描述 Philippe Flaj ...

  4. 天猫:2019将投入百亿孵化100款破亿新品

    最新款的产品到天猫平台首发,正在成为全球品牌与天猫都越来越重视的一件事情.1月7日,天猫发布了2019年的新品战略,提出"双百计划",要投入百亿规模资源,为品牌新品提供流量曝光.供 ...

  5. 置换与轮换——百囚犯问题

    问题描述 Philippe Flajolet和Robert Sedgewick在2009年提出了"百囚犯问题(100 prisoners problem). 在某个法制不健全的国家,监狱中有 ...

  6. c语言千位数字,C语言怎样提取一个数的十位个位百位千位?

    假设那么数为x,不知道是多少位的. 你可以使用下面的表达式: 个位:x%10:十位:x/10%10:百位:x/100%10:千位:x/1000%10. C语言有以下几种取整方法: 1.直接赋值给整数变 ...

  7. 升序输出三个数_C语言入门经典例题:求100~999的水仙花数

    ​C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制). C++,首要考虑的 ...

  8. 请问怎样取三位数的百位数,个位数,和十位数呢 (拆分)?

    请问怎样取三位数的百位数,个位数,和十位数呢 (拆分)? 看百位除以100 去个整数 整数就是百位数看十位 先取余数 比如123 /100 余数为23 再除以10 取整个位数 固定写法 取余数 < ...

  9. C语言:从键盘输入一个整数,分别输出它的个位数、十位数、百位数.....

    /*从键盘输入一个整数,分别输出它的个位数.十位数.百位数*/ #include <stdio.h> #include <conio.h> void main(void) {i ...

最新文章

  1. 在LoadRunner向远程Linux/Unix执行命令行并收集性能数据
  2. theano 安装杂记
  3. 【业务知识】档案工作流程
  4. .bashrc和.vimrc以及一些比较有用的linux命令
  5. java计算八皇后_八皇后java算法
  6. .NET中委托写法的演变(上):委托与匿名方法
  7. 调试实战 —— dll 加载失败之 Debug Release争锋篇
  8. vue 一个页面有点请求需要同时发送_前端性能优化,这些你都需要知道
  9. 不输入密码执行sudo命令方法介绍
  10. open-falcon之query
  11. 问题二:用C++输出第一张图片
  12. Synchronized与ReentrantLock区别总结(简单粗暴,一目了然)
  13. PyTorch中文教程 | (11) 聊天机器人教程
  14. [gitlab] 解决:remote: Ask a project Owner or Maintainer to create a default branch:
  15. H5游戏-面试问题知识点总结
  16. Coredata的版本升级
  17. java多线程访问beans对象_springboot在多线程中注入对象
  18. Mac 卸载 隐蔽软件 Core_Sync 的步骤
  19. 免费的uml建模工具
  20. 华为音乐·DigiX Talk论坛邀你聆听乐动不凡

热门文章

  1. centos java 安装
  2. 附录C Java编程简史
  3. vue3项目安装使用scss
  4. js+css+html制作简易留言板
  5. 离心机 空压机 ZH
  6. Ruoyi用户菜单权限
  7. html每打开一次更换一次域名,网站更换域名空间的注意事项
  8. Office 365系列:配置Outlook IMAP方式连接ExchangeOnline
  9. 572 另一棵树的子树
  10. HTML中的文本标签及样式