问题描述
有 1000 杯水,其中 1 杯水有毒。老鼠只要喝到有毒的一滴,2 小时后死亡。
假定每杯水有足够分量,而且提供充足的可用的取样设备。
如果要求只花费 2 个小时,请问需要至少几只老鼠才能找出哪杯水是有毒的?
问题分析1
这个问题比较直观,还是比较好理解的,这个问题求解算法的好坏可以通过两个因素进行评估:时间花费以及老鼠花费。
很明显问题对时间花费以及老鼠花费是有要求的,可以选择先找到直观的算法,再通过分析并改进直观的算法从而得到符合要求的算法。
问题初步抽象
对于水:我从 0 开始给每 1 杯水进行编号,0 作为第 1 杯水的编号,999 作为第 1000 杯(最后 1 杯)水的编号。另外将 0 号杯水简写为 0 号水以便于描述,其他编号的每 1 杯水同理。
对于老鼠:我从 0 开始给花费的每一只老鼠进行编号,0 作为第 1 只花费的老鼠的编号,其他编号的老鼠描述同理。
问题初步求解
秉着循序渐进的原则,先不考虑时间的花费,也不考虑老鼠的花费。
于是问题可以变为如何花费一定数量的时间以及一定数量的老鼠从 1000 杯水中,找到那杯有毒的水。
这个问题相当直观,可以较为轻松的找到如下算法:
直观算法1
1.使用 1 只老鼠从 第 1 杯水开始喝水,喝完水之后等待 2 小时
2. 2 小时后如果老鼠死亡,那么老鼠 2 小时之前喝的那杯水就是有毒的,如果没死那么这只老鼠继续喝下 1 杯水,直至喝完最后 1 杯水或者死亡
直观算法2
1.使用 1000 只老鼠,每只老鼠各自喝和自己编号相同的那杯水,喝完水后等待 2 小时
2. 2 小时后死亡那只老鼠,所喝的那杯水就是有毒的
算法1评估
最坏时间花费: 2000 小时(可以改进为 1998小时)
老鼠数量花费: 1 只
算法2评估
最坏时间花费: 2 小时
老鼠数量花费: 1000只(当然可以改进为 999 只)
问题分析2
上述 2 个算法都很直观,但并不高效。由于算法 2 相比算法 1 更为接近题目要求(时间上只花费了 2 小时),这里将着重分析并改进算法 2 。
算法 2 之所以需要花费 1000 只老鼠,是因为每只老鼠只喝了 1 杯水,换句话说 1 只老鼠只存储了一杯水的是否有毒的信息。如果能让每只老鼠喝更多杯水,从而能存储更多杯水的是否有毒的信息,那么就能降低老鼠花费。
当有 4 杯水时,可以使用 2 只老鼠找出有毒的那杯水;0 号老鼠可以同时喝下 0 ,1 号水,1 号老鼠可以同时喝下 0,2 号水,可以根据 0 号以及 1 号老鼠的“反应”找到有毒的那杯水。具体就是:如果 2 只老鼠都死亡,那么有毒的那杯水是 0 号;如果 2 只老鼠都存活,那么有毒的那杯水就是 3 号; 如果 0 号老鼠存活且 1 号老鼠死亡,那么 2 号水是有毒的;如果 1 号老鼠都存活且 0 号老鼠死亡,那么 1 号水是有毒。由此可以得出结论:花费 2 只老鼠就可以找出 4 杯水中有毒的那 1 杯。
按照这样的方法,花费 500 只老鼠就解决这个问题!至此又得到 1 个新的算法,这里称为 算法3 !
算法3
使用 500 只老鼠,2 只老鼠为 1 组,共计 250 组
1. 0 号老鼠喝 0,1 号水;1 号老鼠喝 0,2 号水。
2 号老鼠喝 4,5 号水;3 号老鼠喝 4,6 号水。
……
直至,498 号老鼠喝 996,997 号水;499 号老鼠喝 996,998 号水
2. 当有老鼠死亡时,结合这只老鼠所在组别的另一只老鼠的存活情况,就能找出有毒的那杯水
算法3评估
最坏时间花费: 2 小时
老鼠数量花费: 500只
问题分析3
算法 3 任意一组老鼠存活情况,只能判断该组所喝的水是否有毒,对于其他编号的水帮不上忙。
4 杯水的问题可以由 2 个老鼠组成 1 组进行解决。那么这里假设 1000 杯水的问题同样存在 N 只老鼠组成的 1 组进行解决,任意 1 杯水是否有毒,由这 N 只共同决定!
问题进一步抽象
令老鼠喝水用 0 表示,不喝用 1 表示;
生存用 1 表示,老鼠死亡用 0 表示,
所有喝水情况以及生存情况都按老鼠编号从左至右排列;
当有 4 杯水时,使用了 2 只老鼠。
对于 0 号水,2 只老鼠都喝,定义为 0,0
对于 1 号水,0 号老鼠喝,1 号老鼠不喝,定义为 0,1
对于 2 号水,0 号老鼠不喝,1号老鼠喝,定义为 1,0
对于 3 号水,0 号老鼠不喝,1 号老鼠也不喝,定义为 1,1
可以将 0~3 号水,换作如下二进制形式:
00,
01,
10,
11
老鼠喝水情况为:
0 号老鼠喝 0,1 号水;
1 号老鼠喝 0,2 号水;
老鼠生存情况有以下 4 种:
0 号老鼠死亡,1 号老鼠死亡;用 0,0 表示,此时 0 号水有毒。
0 号老鼠死亡,1 号老鼠存活;用 0,1 表示,此时 1 号水有毒。
0 号老鼠存活,1 号老鼠死亡;用 1,0 表示,此时 2 号水有毒。
0 号老鼠存活,1 号老鼠存活;用 1,1 表示,此时 3 号水有毒。
可以发现 2 只老鼠的生存情况和水的编号 一一对应。
这里可以将生存情况,当做二进制,将它转为 10 进制,此时它的值就是有毒的那杯水的编号
当有 8 杯水时,根据上面的结论。使用 4 只老鼠可以找出有毒的那杯水,但不太可能是高效的方法,毕竟 8 杯水杯分成了 2 个单独组。那如果使用 3 只能找到吗?假定可以找到,这里按照 4 杯水的处理方式推导一下:
对于 0 号水,0,1,2 号老鼠都喝,定义为 0,0,0
对于 1 号水,0 号老鼠喝,1 号老鼠喝,2 号老鼠不喝,定义为 0,0,1
对于 2 号水,0 号老鼠喝,1 号老鼠不喝,2 号老鼠喝,定义为 0,1,0
对于 3 号水,0 号老鼠喝,1 号老鼠不喝,2 号老鼠不喝,定义为 0,1,1
对于 4 号水,0 号老鼠不喝,1 号老鼠喝,2 号老鼠喝,定义为 1,0,0
对于 5 号水,0 号老鼠不喝,1 号老鼠喝,2 号老鼠不喝,定义为 1,0,1
对于 6 号水,0 号老鼠不喝,1 号老鼠不喝,2 号老鼠喝,定义为 1,1,0
对于 7 号水,0,1,2 号老鼠都不喝,定义为 1,1,1
老鼠喝水情况为:
0 号老鼠喝 0,1,2,3 号水;
1 号老鼠喝 0,1,4,5 号水;
2 号老鼠喝 0,2,4,6 号水;
老鼠生存情况有以下 8 种:
0 号老鼠死亡,1 号老鼠死亡,2 号老鼠死亡;用 0,0,0 表示,此时 0 号水有毒。
0 号老鼠死亡,1 号老鼠死亡,2 号老鼠存活;用 0,0,1 表示,此时 1 号水有毒。
0 号老鼠死亡,1 号老鼠存活,2 号老鼠死亡;用 0,1,0 表示,此时 2 号水有毒。
0 号老鼠死亡,1 号老鼠存活,2 号老鼠存活;用 0,1,1 表示,此时 3 号水有毒。
0 号老鼠存活,1 号老鼠死亡,2 号老鼠死亡;用 1,0,0 表示,此时 4 号水有毒。
0 号老鼠存活,1 号老鼠死亡,2 号老鼠存活;用 1,0,1 表示,此时 5 号水有毒。
0 号老鼠存活,1 号老鼠存活,2 号老鼠死亡;用 1,1,0 表示,此时 6 号水有毒。
0 号老鼠存活,1 号老鼠存活,2 号老鼠存活;用 1,1,1 表示,此时 7 号水有毒。
至此 8 杯水问题,可以用 3 只老鼠解决。
2 只老鼠的 4 种存活情况对应了 4 杯水的各自编号;
3 只老鼠 8 种存活情况分别对应了 8 杯水各自编号。
当有 N 只老鼠时,由于每只老鼠的存活情况有 0,1 两种,于是共有 2 的 N 次方种存活情况,可以解决大小为 2的N次方的问题。
当 N 为 10 时,存活情况为 1024 种。
至此,对于 1000 杯水,只需要花费 10 只老鼠就能找到有毒的那 1 杯水。
算法4
对于 1000 杯水,使用 10 只老鼠,
将 0~999 编号的每 1 杯水编号写作二进制形式
从 0000000000 到 11111100111 对于每 1 杯水,将每 1 杯水编号的二进制的形式 10 位数字,从左到右的分别描述为第 1 位到第 10 位
0 号老鼠喝 第 1 位为 0 的二进制编号所对应编号的水,
1 号老鼠喝 第 2 位为 0 的二进制编号所对应编号的水,
……
8 号老鼠喝 第 9 位为 0 的二进制编号所对应编号的水,
9 号老鼠喝 第 10 位为 0 的二进制编号所对应编号的水,
2 小时后,将表示存活情况的 10 位二进制数,转为 10 进制,就找到了有毒的水的编号
算法4评估
最坏时间花费: 2 小时
老鼠数量花费: 10 只