小白鼠毒药问题(代码实现)
问题描述
现在有100瓶药水,其中只有一瓶是毒药,只需一滴就能够杀死任何生物,其他的是正常的药水,并且你无法通过气味或者颜色辨别出来,你只有若干只小白鼠来做实验。请问如何使用尽可能少的小白鼠并且尽可能快地来准确的辨别出那瓶有毒的药水。
问题分析
拿到问题之后,立马能够想到的方法便是使用一百只小白鼠,每瓶药水喂给不同的小白鼠,最后观察哪只小白鼠死亡即可判断出哪瓶药水是毒药。这种方法显然非常暴力,但是很省时间。或者还有一些其他的解法,比如将一百瓶药水分成十组,每组十瓶,每组的每瓶都取出一点药水喂给不同的小白鼠,这种方法确实能够比最暴力的方法节省很多白鼠开销,但是弊端在于不能一次性找到,也就是后续这个方法还得根据小白鼠的情况继续将某组药水进行分组,时间上并不是最优。
问题求解-二进制编码搜索
现在介绍一种二进制编码的方法用于寻找毒药瓶的编号。我们知道100的二进制是1100100,因此只需要七位二进制编码就可以将100瓶药水全部编号,而我们需要使用的小白鼠也只需要七只并且一次性就可以找出毒药瓶的编号。
Step1: 将100个药瓶标号1~100,并且是二进制的编号,如果二进制编号不足七位,就在前面补0,比如10的二进制是1010,只有四位,补齐七位为00001010,以此类推。
Step2: 准备七只小白鼠,并且标号1~7,然后分别将药瓶的二进制标号的1 ~7位为1的药瓶混合一部分喂给对应标号的小白鼠,比如标号为1的小白鼠,喂给它药瓶标号中第一位为1(从左往右数)的所有药瓶中药水(只需要一小部分药水即可),标号为2的小白鼠,喂给它第二位为1的所有药瓶中的药水,以此类推。
Step3: 观察这七只小白鼠的死亡情况,如果1,3,5标号的小白鼠死亡,其余小白鼠正常,说明药瓶标号的第一三五位为1的那瓶药水有毒,其余位都是零,因为如果其余位还有1的话,肯定还会有其他的小白鼠死亡,因为每只小白鼠喂的都是对应标号位置为1的药瓶中的药水,那么可以得到毒药瓶的标号为1010100,也就是编号为84的药瓶是毒药瓶。
代码实现
首先我们来实现一些工具类中的方法,首先是判断小白鼠是否死亡的代码:
public static boolean isDead(Mouse mouse,String StrPoisonNumber){
int id=mouse.getId();
List<MedicineBottle> drugDelivery = mouse.getDrugDelivery();
char c = StrPoisonNumber.charAt(id);
for(int i=0;i< drugDelivery.size();i++){
MedicineBottle medicineBottle = drugDelivery.get(i);
String BottleId = medicineBottle.getId();
char c1 = BottleId.charAt(id);
if(c1=='1'&&c=='1'){
mouse.setDead(true);
return true;
}
}
mouse.setDead(false);
return false;
}
接下来是给不同编号小白鼠喂药的代码,喂药规则:对应编号的小白鼠所喂的药来自于药瓶二进制编号的对应位置的1的药瓶,详情请看问题求解部分:
public static Mouse[] Delivery(Mouse[] mice,MedicineBottle[] medicineBottles){
for(int i=0;i< mice.length;i++){
Mouse mouse = mice[i];
int id=mouse.getId();
List<MedicineBottle> drugDelivery = mouse.getDrugDelivery();
for(int j=0;j< medicineBottles.length;j++){
if(medicineBottles[j].getId().charAt(id)=='1'){
drugDelivery.add(medicineBottles[j]);
// System.out.println("添加");
}
}
// System.out.println(drugDelivery.size());
}
return mice;
}
最后一个工具类的方法是用于获取有毒药瓶编号的二进制不为1的下标:
public static List<Integer> getPoisonNumber(Mouse[] delivery,String StrPoisonNumber){
List<Integer> result=new ArrayList<>();
for(int i=0;i< delivery.length;i++){
Mouse mouse = delivery[i];
boolean dead = isDead(mouse, StrPoisonNumber);
if(dead==true){
result.add(i);
}
}
return result;
}
现在我们开始编写主函数,首先我们需要生成七只小白鼠,用于样本毒性测试,并且随机生成一个有毒药瓶的编号,我们还需要生成一百个药水瓶,并将有毒那瓶设置属性isPoison为true,其余为false。最后我们调用工具类中各个方法来获取有毒编号的二进制位为1的下标:
public static void main(String[] args) {
Mouse[] mice=new Mouse[7];
for(int i=0;i<7;i++){
Mouse mouse=new Mouse();
mouse.setId(i);
mouse.setDead(false);
mice[i]=mouse;
}//生成七只老鼠对象,用于样本毒性测试
Random random = new Random();
int PoisonNumber=random.nextInt(100);//随机生成有毒药瓶的编号
// System.out.println("生成的毒药瓶的编号;"+PoisonNumber);
String StrPoisonNumber=Integer.toBinaryString(PoisonNumber);
while (StrPoisonNumber.length()<7){
StrPoisonNumber="0"+StrPoisonNumber;
}//补齐位数
MedicineBottle[] medicineBottles=new MedicineBottle[100];
for(int i=0;i<100;i++){
MedicineBottle medicineBottle = new MedicineBottle();
String id=Integer.toBinaryString(i);
while (id.length()<7){
id="0"+id;
}
medicineBottle.setId(id);
if(i==PoisonNumber){
medicineBottle.setPoison(true);
}
else {
medicineBottle.setPoison(false);
}
medicineBottles[i]=medicineBottle;
}//生成一百个药瓶对象,并将有毒那瓶的isPoison设置为true
Mouse[] delivery = utils.Delivery(mice, medicineBottles);//将药瓶中的药按照规则分配给七只老鼠。
List<Integer> poisonNumber = utils.getPoisonNumber(delivery, StrPoisonNumber);
String str="0000000";
char[] chars = str.toCharArray();
for(int i=0;i< poisonNumber.size();i++){
chars[poisonNumber.get(i)]='1';
}
// System.out.println(poisonNumber);
String s = new String(chars);
// System.out.println(s);
int i = Integer.parseInt(s,2);
System.out.println("有毒的药瓶编号为:"+i);
}
总结
小白鼠毒药问题是一个经典的概率问题,第一次遇到这个问题往往会无从下手,方法是我借鉴来的,之前看到这个解法非常感兴趣,今天抽空将代码进行了实现,至于文中提到的100瓶药瓶需要七只小白鼠的说法,那么为什么是七只,可以参考这篇博文小白鼠试毒问题(关于为什么是七只,文中有介绍)。代码链接:小白鼠毒药问题完整代码,提取码:java.欢迎大家批评指正。