实验题目
在8枚外观相同的硬币中,有一枚是假币,并且已知假币与真币的重量不同,但不知道假币与真币相比较轻还是重。可以通过一架天平来任意比较两组硬币,设计一个高效的算法来检测这枚假币。
实验目的
1.深刻理解并掌握减治法的设计思想;
2.提高应用减治法设计算法的技能;
3.理解这样一个观点:建立正确的模型对于问题的求解释非常重要的。
实验要求
1.设计减治法实现8枚硬币问题;
2.设计实验程序,考察用减治法技术设计的算法是否高效;
3.扩展算法,使之能处理n枚硬币中有一枚假币的问题。
算法设计与实现
从八枚硬币中任取六枚
a,b,c,d,e,f
a
,
b
,
c
,
d
,
e
,
f
,在天平两端各放三枚进行比较。假设
a,b,c
a
,
b
,
c
三枚放在天平的一端,
d,e,f
d
,
e
,
f
三枚放在天平的另一端,可能出现三种比较结果:
⑴a+b+c>d+e+f
⑴
a
+
b
+
c
>
d
+
e
+
f
⑵a+b+c=d+e+f
⑵
a
+
b
+
c
=
d
+
e
+
f
⑶a+b+c<d+e+f
⑶
a
+
b
+
c
<
d
+
e
+
f
若
a+b+c>d+e+f
a
+
b
+
c
>
d
+
e
+
f
,可以肯定这六枚硬币中必有一枚为假币,同时也说明,
g,h
g
,
h
为真币。这时可将天平两端各去掉一枚硬币,假设去掉
c
c
和,同时将天平两端的硬币各换一枚,假设硬币
b
b
和作了互换,然后进行第二次比较,比较的结果同样可能有三种:
①
a+e>d+b
a
+
e
>
d
+
b
: 这种情况表明天平两端去掉硬币
c,f
c
,
f
且硬币
b,e
b
,
e
互换后,天平两端的轻重关系保持不变,从而说明了假币必然是
a,d
a
,
d
中的一个,这时我们只要用一枚真币(例如
h
h
)和进行比较,就能找出假币。若
a>h
a
>
h
,则
a
a
是较重的假币;若,则
d
d
为较轻的假币;不可能出现
②
a+e=d+b
a
+
e
=
d
+
b
:此时天平两端由不平衡变为平衡,表明假币一定在去掉的两枚硬币
c,f
c
,
f
中,同样用一枚真币(例如
h
h
)和进行比较,若
c>h
c
>
h
,则
c
c
是较重的假币;若,则
f
f
为较轻的假币;不可能出现
③
a+e<d+b
a
+
e
<
d
+
b
: 此时表明由于两枚硬币
b,e
b
,
e
的对换,引起了两端轻重关系的改变,那么可以肯定
b
b
或中有一枚是假币,同样用一枚真币(例如
h
h
)和进行比较,若
b>h
b
>
h
,则
b
b
是较重的假币;若,则
e
e
为较轻的假币;不可能出现
对于结果(2)和(3)的情况,可按照上述方法作类似的分析。
下图给出了判定过程,图中大写字母
H
H
和分别表示假币较其它真币重或轻,边线旁边给出的是天平的状态。八枚硬币中,每一枚硬币都可能是或轻或重的假币,因此共有16种结果,反映在树中,则有16个叶子结点,从下图中可看出,每种结果都需要经过三次比较才能得到。
![](https://i-blog.csdnimg.cn/blog_migrate/da0023b002790cba8f3dd79d44549e12.jpeg)
在n枚硬币问题中,同样应用减治法的思想,将硬币分为3堆,则每堆的硬币数量为 n/3 ,但是这在 n%3==0 的情况下才能成立,所以我们将 n 枚硬币分为 3 堆加 1 个余数堆(余数堆可能为0),则可分为如下(n-n%3)/3,(n-n%3)/3,(n-n%3)/3,n%3。
逻辑流程:
1. 首先获取真币,通过从数组中随机取三枚硬币,互相比较,相等的两枚为真币,任意取一枚作为真币记录数组下标。
2. 判断n中的硬币数量,如果n>2则执行3,否则执行6.
3. 将n分为上图的四堆,拿 a 和 b 比较,如果 a == b ,则 假币在 c 或 d 中。否则假币在 a 或 b 中。
4. 如果 a == b,则拿 a 和 c 比较。如果 a == c,则假币在d(余数堆)中。将 d 再次 执行流程2,并且n=n%3。如果不等,则假币在 c 中,将 c 再次 执行流程2,并且n=(n-n%3)/3。
5. 如果 a != b,则拿 a 和 c 比较。如果 a == c,则假币在b中,将 b 再次 执行流程2,并且n=(n-n%3)/3。如果不等,则假币在 a 中,将 a 再次 执行流程 2,并且n=(n-n%3)/3。
6. 如果n==2,则将两枚硬币与真的硬币(通过 数组下标 )进行比较,不同的为假币,输出结果,结束。
7. 如果n==1,则该硬币就是假币,输出结果结束。
程序源码:
#include<iostream>
using namespace std;
//函数声明
void eightcoin(int arr[]);
void compare(int a, int b,int real, int index1,int index2);
void print(int fake, int real, int i);
int main()
{
int arr[8];
//这里输入a、b、c、d、e、f、g、h的重量
cout<<"请输入八枚硬币:"<<endl;
for(int i = 0; i < 8; i++)
{
cin>>arr[i];
}
eightcoin(arr);
return 0;
}
void eightcoin(int arr[])
{
//取数组中的前6个元素分为两组进行比较abc,def
//会有a+b+c > d+e+f | a+b+c == d+e+f | a+b+c < d+e+f 三种情况
int abc = arr[0] + arr[1] + arr[2];
int def = arr[3] + arr[4] + arr[5];
int a = arr[0];
int b = arr[1];
int c = arr[2];
int d = arr[3];
int e = arr[4];
int f = arr[5];
int g = arr[6];
int h = arr[7];
if(abc > def) //6枚硬币必有一枚假币,g,h为真币
{
if((a + e) > (d + b)) //去掉c,f,且b,e互换后,没有引起天平变化,说明假币必然是a,d中的一个
{
compare(a,d,g,0,3);
}
else if((a + e) == (d + b))
{
compare(c,f,g,2,5);
}
else
{
compare(b,e,g,1,4);
}
}
else if(abc == def) //假币在g,h之中,最好状态
{
if(g == a)
{
print(h,g,7);
}
else
{
print(g,h,6);
}
}
else //abc < def 这两组存在一枚假币,g,h为真币
{
if((a + e) > (d + b))
{
compare(b,e,g,1,4);
}
else if((a + e) == (d + b))
{
compare(c,f,g,2,5);
}
else
{
compare(a,d,g,0,3);
}
}
}
/**
* 取出可能有一枚假币的两枚假币,作为参数a和参数b
* real表示真币的重量,index1为第一枚硬币的下标,index2为第二枚硬币的下标
*/
void compare(int a, int b,int real, int index1,int index2)
{
if(a == real)
{
print(b,real,index2);
}
else
{
print(a,real,index1);
}
}
void print(int fake, int real, int i)
{
if(fake > real)
{
cout<<"位置在:"<<(i + 1)<<"是假币!"<<"且偏重!";
}
else {
cout<<"位置在:"<<(i + 1)<<"是假币!"<<"且偏轻!";
}
}
实验结果与分析
1.实验结果
![](https://i-blog.csdnimg.cn/blog_migrate/39d4e3cf6cc135e883761986ed987824.png)
2.实验分析
减治法是把一个大问题划分为若干个小问题,但是这些子问题不需要分别 ,只需要求解其中的一个子问题,因而也无须对子问题的解进行合并,这样大大提高了算法的效率。
在八枚硬币问题中,应用减治法,将问题一分为二,这样只需要3次比较便能解决问题。
对于n枚硬币问题,同样可以用减治法处理,采用分治和递归的方法逐步接近问题的解。