问题描述:
将两片芯片放在测试台上相互测试(比如 A 芯片测试 B,B 芯片测试 A),并且给出测试报告是 “好” 或者 “坏”。
假设:好芯片报告一定是正确的;坏芯片报告不一定是正确的(比如有可能报告 “好”,也有可能报告 ”坏“)。
测试结果:
序号 | A的报告 | B的报告 | 推测结果 |
---|---|---|---|
① | B “好” | A “好” | 两片都是好的或两片都是坏的 |
② | B “好” | A “坏” | 至少有一片是”坏“的 |
③ | B “坏” | A ”好“ | 至少有一片是”坏“的 |
④ | B “坏” | A “坏” | 至少有一片是”坏“的 |
对测试结果推导:
-
推导①:
- 假设A本身为“好”,则A报告正确,则B是“好”,此时二者都是“好”;
- 假设A本身为“坏”,而此时B报告说A是”好“,说明B是”坏“,而A报告说B是“好”,说明A也是“坏”,符合假设此时二者都是“坏”。
-
推导②:
-
假设A本身为”好“,则A报告正确,说明B是“好”,但B报告A为“坏”,与假设冲突;
-
假设A本身是”坏“,则B有可能是”好“也有可能是“坏”。
-
综上:此时一定是至少有一片是”坏“的。
-
-
推导③:
- 假设B本身为”好“,则B报告正确,说明A是“好”,但A报告B为“坏”,与假设冲突;
- 假设B本身是”坏“,则A有可能是”好“也有可能是“坏”。
- 综上:至少有一片是”坏“的。
-
推导④:
- 首先不可能两个都是”好“的;
- 假设A本身是”好“,则A报告正确,则B是”坏“,成立;
- 假设A本身是”坏“,则B有可能是”好“也有可能是“坏”。
- 综上:此时一定是至少有一片是”坏“的。
实例:
一、 题目要求:
输入芯片数目为n,以及n片芯片的状态(“好” 或者 ”坏“)。
假设:
- “好” 芯片状态输入为1;“坏” 芯片状态输入为0。
- ”好“ 芯片至少比 “坏” 芯片多一片。
通过测试从n片芯片中找到一片 ”好“ 芯片,并输出其编号(编号从 1 号开始),要求测试次数最少。
二、 分治思想:
1. 假设n为偶数时:
①. 测试方法:
将n片芯片分成n/2组,两两分成一组进行测试,并根据淘汰规则淘汰芯片,将留下来的芯片再进行分组测试,这样就将原问题分解为子问题了。
②. 淘汰规则:
序号 | A的报告 | B的报告 | 推测结果 | 淘汰规则 |
---|---|---|---|---|
① | B “好” | A “好” | 两片都是好的或两片都是坏的 | 任留一片进入下一轮 |
② | B “好” | A “坏” | 至少有一片是”坏“的 | 全部扔掉 |
③ | B “坏” | A ”好“ | 至少有一片是”坏“的 | 全部扔掉 |
④ | B “坏” | A “坏” | 至少有一片是”坏“的 | 全部扔掉 |
**③. 注意:**根据上述淘汰规则进行递归的过程中为什么会一直保证 ”好“ 芯片永远要比 ”坏“ 芯片至少多一片?
④. 证明:假设分组后情况如下:
- 刚好有 i 组两片都是 ”好“ 的芯片被分在了一起,此时报告会出现第①种情况,所以扔一半留一半,剩 i/2 片;
- 刚好有 j 组 一”好“一“坏” 的芯片被分在了一起,此时报告会出现第②③④种情况,所以全扔,剩 0 片;
- 刚好有 k 组两片都是 “坏” 的芯片被分在了一起,此时报告会出现第①②③④种情况,所以会出现有的组全扔,而有的组扔一片留一片的情况,因此最多会剩 k/2片(此时 k 组全部报告为第①种情况)。
因此,根据上述淘汰规则进行递归的过程中会一直保证 ”好“ 芯片永远要比 ”坏“ 芯片至少多一片
初始芯片数为: | 2i + 2j + 2k = n |
---|---|
初始分组后: | ”好“ “坏” |
2i + j > 2k + j | |
经过一轮淘汰后剩余的芯片数: | i > k |
至此,根据 ”好“ 芯片永远要比 ”坏“ 芯片至少多一片 可以得到递归的截至条件为:芯片数 n≤2,此时,剩余的芯片就都是好芯片,此时就不用再递归。
2. 假设n为奇数时:
①. 存在的问题:
当n为奇数时,会出现最后一片芯片轮空,如果将轮空芯片直接进入下一轮,会将 ”好“ 芯片永远要比 ”坏“ 芯片至少多一片 这个条件破坏,比如 n=7时,由下图可知此时经过一轮淘汰后出现了留下来的芯片出现了 ”好“ 芯片数和 “坏” 芯片数相等的情况,所以,对轮空芯片不能采用直接进入下一轮的方法。
②. 解决方法:
将n片芯片分成n/2组,两两分成一组进行测试时,出现最后一片芯片轮空。此时,让轮空芯片去测试所有的前 n-1个芯片,这样会得到n-1个报告,根据报告判断轮空芯片是否留下。
假设n=7时,第7片芯片轮空,此时,让第7片芯片测试前6片芯片,这样会得到6份报告:
对6份报告推导 | 结果 | 推广到 n(n为奇数)片芯片的情况 |
---|---|---|
当6份报告中至少3个报 “好” 时 | 轮空芯片为 “好” | 至少有**(n-1)/2** 份报告 “好”,轮空芯片为 “好” |
当6份报告中至少4个报 “坏” 时 | 轮空芯片为 “坏” | 至少有**(n+1)/2** 份报告 “坏”,轮空芯片为 “坏” |
对于轮空芯片的淘汰规则:当轮空芯片为 “好” 时,将轮空芯片留下,进入下一轮;当轮空芯片为 “坏” 时,将轮空芯片扔掉。这样在递归过程中 ”好“ 芯片永远要比 ”坏“ 芯片至少多一片 这一条件就不会被破坏。
代码实现:
import java.util.Scanner;
public class 分治_6芯片测试问题
{
// 当n是奇数时,将前n - 1个芯片分别和轮空的芯片(第n个芯片)进行测试
// X为前n-1个芯片,Y为轮空的芯片(第n个芯片)
public static boolean Jtest(int X, int Y)
{
// 如果X为坏,Y为好,则返回坏
if (X == 0 && Y == 1)
return false;
// 否则,如果X为好,Y为好,则返回好
else if (X == 1 && Y == 1)
return true;
// 如果Y为坏,则返回一个随机值
else
return ((int) Math.random() * 2 == 1);
}
// 模拟测试台,两两分组后进行测试
public static boolean[] test(int A, int B)
{
// result 用来存测试结果
boolean[] result = new boolean[2];
// 假设刚好都为好的芯片分在一组时的结果
if (A == 1 && B == 1)
{
result[0] = true;
result[1] = true;
}
// 假设刚好一好一坏的芯片分在一组时的结果
if ((A == 1 && B == 0) || (A == 0 && B == 1))
{
result[0] = false;
result[1] = ((int) Math.random() * 2 == 1);
}
// 假设刚好都为坏的芯片分在一组时的结果
if (A == 0 && B == 0)
{
result[0] = ((int) Math.random() * 2 == 1);
result[1] = ((int) Math.random() * 2 == 1);
}
return result;
}
//
public static int[][] getRight(int[][] arr, int length)
{
// 截至条件:当前芯片数 length≤2 ,此时剩余的芯片就都是好芯片。
if (length <= 2)
{
int[][] a = new int[length][length];
for (int i = 0; i < length; i++)
{
a[i] = arr[i];
}
return a;
} else // 此时当前芯片数 length≥3 时,没有到达截至条件
{
// temp 数组用来存放进入下一轮的芯片
int[][] temp = new int[(length / 2) + 1][(length / 2) + 1];
// result 数组用来存每次测试后返回的报告
boolean[] result = null;
// count 表示进入下一轮测试的芯片数
int count = 0;
// 当前芯片数length为偶数
for (int i = 0; i < length - 1; i = i + 2)
{
// 两两一组进行测试
result = test(arr[i][1], arr[i + 1][1]);
// 如果报告出现两个都是 “好”,则任留一个进入下一轮,否则,全部扔掉
if (result[0] == true && result[1] == true)
{
int x = (int) (Math.random() * 2);
// 将留下的芯片的编号和状态(“好”或者“坏”)都存入temp数组中
temp[count] = arr[i + x];
// 由于用来存芯片的数组是一个二维数组,所以每次留一个芯片时,count要+2
count = count + 2;
}
}
// 当芯片数为奇数时,会出现有一个芯片轮空,让空芯片测试前length-1
if (length % 2 == 1)
{
// jishu 数组用来存轮空芯片对前length-1个芯片测试的报告
boolean[] jishu = new boolean[length - 1];
for (int i = 0; i < length - 1; i++)
jishu[i] = Jtest(arr[i][1], arr[length - 1][1]);
// True 用来记录报告为“好”的数量
int True = 0;
for (int i = 0; i < length - 1; i++)
{
if (jishu[i] == true)
True++;
}
// 当 True≥(length-1)/2 时,说明轮空芯片为“好”,将轮空芯片留下,进入下一轮;否则扔掉
if (True >= (length - 1) / 2)
{
temp[count] = arr[length - 1];
count = count + 2;
}
}
// 递归调用,进入下一轮
return getRight(temp, count / 2);
}
}
public static void main(String[] args)
{
// TODO 自动生成的方法存根
Scanner input = new Scanner(System.in);
// 输入初始芯片数 n
int n = input.nextInt();
// 用数组模拟一个n行2列的表,第一行存芯片对应的编号,第2列存芯片的状态(“好”或者“坏”)
int[][] arr = new int[n][2];
if (n == 1)
System.out.println("最先找到的 “好” 芯片编号(从1号开始):" + '1' + "号");
else
{
for (int i = 0; i < n; i++)
{
arr[i][0] = i + 1;
arr[i][1] = input.nextInt();
}
input.close();
int[][] temp = null;
temp = getRight(arr, n);
System.out.println("最先找到的 “好” 芯片编号(从1号开始):" + temp[0][0] + "号");
}
}
}