白鼠试毒酒问题

这道题有两种问法,一种是问需要多少只老鼠才能确定,一种是问要如何安排老鼠的喝法。

第一种问法相对简单:
1000 瓶无色无味的白酒,其中有一瓶毒酒, 白鼠喝了毒酒一个星期(或一天,无所谓)后会死去。 那么问你:最少需要多少只白鼠,可以在最短时间内(一个星期或者一天,反正只能实验一次)即可找出那瓶毒酒。

第二种问法比较更难一点:同样1000瓶白酒(其中只有一瓶毒酒),用10只小白鼠拿过来做实验。如何在最短时间(一周或一天,反正只能做一次实验)之内找出这瓶有毒的药水?

首先看第一种问法。

最少需要多少只老鼠?

其实是一个编码问题。1000 瓶白酒如果不考虑成本问题(即老鼠数目没有限制),那么用 1000 只老鼠分别喝一瓶,很容易确定那瓶白酒有毒。那只老鼠死了,就是那瓶酒有问题。但是实验结果就会有 1000 份。

1000 个实验数据太大了,我们可以缩小实验规模,然后类推。假设有 4 瓶白酒,用 4 只小鼠来做实验。

那么分别让每只老鼠喝一瓶白酒,那么可能得出 4 种结果(排列组合):

第1种结果:x o o o

第2种结果:o x o o

第3种结果:o o x o

第4种结果:o o o x

在这个表格中,每一行表示一种可能的实验结果。在每一种结果中,使用了 4 个 o 或 x 来分别表示 4 只老鼠的死/活状态。打 x 表示这只老鼠死了,打 o 则表示老鼠还活着。这样,只消看第几只老鼠的位置上打了 x,就知道是哪瓶白酒有问题。

如果你将上表换成用二进制表示,即 x 换成 1,o 换成 0,你会发现它们其实和白酒的编号(转换成二进制)有一定的映射关系:

第1种结果:1 0 0 0 --> 第一瓶酒:1 --> 0 0 0 1

第2种结果:0 1 0 0 --> 第二瓶酒:2 --> 0 0 1 0

第3种结果:0 0 1 0 --> 第三瓶酒:3 --> 0 1 0 0

第4种结果:0 0 0 1 --> 第四瓶酒: 4 --> 1 0 0 0

其实无非就是老鼠的编号的高低位和白酒编号的高低位顺序相反了。如果我们实验结果的编码颠倒一下,也按照“高位在前”的原则编码,那么你会发现,其实老鼠的编号和白酒编号恰恰是一致的:

第1种结果:0 0 0 1 --> 第一瓶酒:1 --> 0 0 0 1

第2种结果:0 0 1 0 --> 第二瓶酒:2 --> 0 0 1 0

第3种结果:0 1 0 0 --> 第三瓶酒:3 --> 0 1 0 0

第4种结果:1 0 0 0 --> 第四瓶酒:4 --> 1 0 0 0

这里我们把白酒按照每瓶白酒占一个二进制位的方式编码,所以有多少瓶白酒,就需要多少位二进制位来编码。

如果白酒的编码用二进制编码需要 4 位,那么就需要用 4 只老鼠来做实验。如果白酒编码的长度为 100 位,那么就需要 100 只老鼠来实验。

但问题是,上面的二进制编码并不是最优的(最短的)。我们知道如果要表示 4 瓶白酒,其实只需要 2 位二进制就足以表示。注意看上面的编码,4 屏白酒分别占用了 4 个 4 位二进制编码: 0001,0010,0100,1000,但除此之外,其实还有 4 个 4 位二进制编码 0000,0011,0101,0111 是没用到的。有整整一半的编码被闲置了,显得有些浪费。

那么要对 4 个数字进行编码,需要多少位二进制就能编完呢?答案是 2 位。因为 22 等于 4。

第 1 瓶酒:0 0

第 2 瓶酒:0 1

第 3 瓶酒:1 0

第 4 瓶酒:1 1

注意,这里为了最大化利用编码,第一瓶酒的编码从 0 开始而非从 1 开始。

那么如果是 10 瓶酒呢?需要几位二进制进行编码?首先 3 位肯定不够(它只能表示 8 个数),4 位稍有点多(16个数),但是 5 位就更多了。所以只能选 4 位。于是要表示 n 个数,只需要计算出最接近这个数(同时不能小于这个数)的 2 的整数次方即可,即存在 2m >= n >= 2m-1 。m 就是二进制数的位数。

因此,1000 瓶酒的编码方案应该是 210 = 1024。于是答案就出来了,1000 瓶酒的实验方案最少需要 10 只老鼠。

每只老鼠喝哪几瓶酒?

其实,实验的方案同样暗示在了瓶子的编码上。还是用 4 瓶白酒作为例子吧:

第1种结果:0 0 0 1 --> 第一瓶酒:1 --> 0 0 0 1

第2种结果:0 0 1 0 --> 第二瓶酒:2 --> 0 0 1 0

第3种结果:0 1 0 0 --> 第三瓶酒:3 --> 0 1 0 0

第4种结果:1 0 0 0 --> 第四瓶酒:4 --> 1 0 0 0

我们把每种‘答案’,也就是实验结果都和一瓶白酒进行了一一匹配(将它们的编码都统一了)。

这样做的好处很明显,酒瓶编码中的二进制序列就暗示了这瓶酒的终极‘答案’,即是否是毒酒的线索。也就是说,如果实验结束后,将实验结果编码成二进制,如果和某只酒瓶的编码一致,则说明这瓶酒就是毒酒。于是实验结果出来后,要知晓哪瓶白酒有问题,只需要将实验结果编码拿去上表中比照一下即可。

但是还不仅仅如此,实验结果的编号同样表明了 4 只老鼠的每一只分别喝了哪瓶酒,也就是实验方案。有 4 种实验结果,也就对应了 4 种实验组合。而且和实验只有一个结果不同,为了尽快出结果(题目中有此要求),我们最好将 4 种实验组合都同时进行测试,这样就能一次性能遍历所有可能的实验结果。不管毒酒是哪一瓶,只需一次测试。

因此上述实验方案就是(4 种实验方案一起进行):

第1种结果:0 0 0 1 --> 第一瓶酒:1 --> 0 0 0 1 --> 给第1只老鼠喝

第2种结果:0 0 1 0 --> 第二瓶酒:2 --> 0 0 1 0 --> 给第2只老鼠喝

第3种结果:0 1 0 0 --> 第三瓶酒:3 --> 0 1 0 0 --> 给第3只老鼠喝

第4种结果:1 0 0 0 --> 第四瓶酒:4 --> 1 0 0 0 --> 给第4只老鼠喝

发现规律了没有?就是实验可能性、酒瓶编码、实验方案一一对应了。

如果用最短编码来实现,就是:

第1种结果:0 0 --> 第一瓶酒:1 --> 0 0 --> 两只老鼠喝

第2种结果:0 1 --> 第二瓶酒:2 --> 0 1 --> 给第1只老鼠喝

第3种结果:1 0 --> 第三瓶酒:3 --> 1 0 --> 给第2只老鼠喝

第4种结果:1 1 --> 第四瓶酒:4 --> 1 1 --> 给两老鼠都喝

注意,这里为了最大化利用编码,第一瓶酒的编码从 0 开始而非从 1 开始。

那么 1000 瓶酒的实验方案,用 C 语言实现其实就是连续打印出 0~999 的二进制数。

// 白鼠试毒问题
void findPoison(int bottles){
    int digits = 0;
    int tmp = bottles - 1;// 有一瓶酒不用试,因为根据其它酒的测试结果,很容易就判断这瓶酒是否有毒
    // 计算需要几只老鼠
    while(tmp > 0){
        tmp = tmp / 2;
        digits ++;
    }
    NSLog(@"%d瓶酒需要几只老鼠:%d",bottles,digits);
    
    // 打印每瓶酒的编号
    for(int i= 0; i < bottles; i++){
        printBits(i, digits);
    }
    
}
// 打印十进制数的二进制形式
void printBits(int a,int digits){
    char ch[digits+1];
    int tmp=a;
    
    for(int i = digits-1;i>= 0;i--){
        if(tmp % 2 == 0){
            ch[i] = '0';
        }else{
            ch[i] = '1';
        }
        tmp = tmp / 2;
    }
    ch[digits] = '\0';
    NSLog(@"%2d ==> %s",a,ch);
}

打印结果类似:

需要几只老鼠:10
0   ==> 0000000000
1   ==> 0000000001
2   ==> 0000000010
3   ==> 0000000011
4   ==> 0000000100
5   ==> 0000000101
6   ==> 0000000110
7   ==> 0000000111
8   ==> 0000001000
......
998 ==> 1111100110
999 ==> 1111100111

实验结果验证

前面说过,“正确答案”都写在酒瓶(编码)上。也就是说每一种实验结果对应了一瓶酒。假设实验结果是这个:

1111100111

即“除了 4、5 两只老鼠外其它老鼠都死了”

那么你可以根据这个编号去查表(或者自己换算成 10 进制):

999 ==> 1111100111

第 999 瓶是毒酒。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值