问题描述:
在n枚外观相同的硬币中,有一枚是假币,并且已知假币与真币的重量不同,但不知道假币与真币相比较轻还是较重。可以通过一架天平来任意比较两组硬币,设计一个高效的算法来检测这枚假币(以下提供两种方法)。
解题思路1(本例为真币重量大于假币):
使用减治法的解题思路,将硬币分为3堆,则每堆的硬币数量为 n/3 ,但是这是在 n%3==0 的情况下才能成立,所以我们将 n 枚硬币分为 3 堆加 1 堆 余数堆(余数堆可能为0),则可分为如下(n-n%3)/3, (n-n%3)/3, (n-n%3)/3, n%3。
如下分组:
a堆: (n-n%3)/3
b堆: (n-n%3)/3
c堆: (n-n%3)/3
d(余数堆): n%3
逻辑流程:
1. 判断n中的硬币数量,如果n>2则执行2,否则执行5.
2. 将n分为上图的四堆,拿 a 和 b 比较,如果 a == b ,则 假币在 c 或 d 中。否则假币在 a 或 b 中。
3. 如果 a == b,则拿 a 和 c 比较。如果 a == c,则假币在d(余数堆)中。将 d 再次 执行流程1,并且n=n%3。如果不等,则假币在 c 中,将 c 再次 执行流程1,并且n=(n-n%3)/3。
4. 如果 a != b,则拿 a 和 c 比较。如果 a == c,则假币在b中,将 b 再次 执行流程1,并且n=(n-n%3)/3。如果不等,则假币在 a 中,将 a 再次 执行流程 1,并且n=(n-n%3)/3。
5. 如果n==2,则将两枚硬币进行比较找出假币。
6. 如果n==1,则该硬币就是假币,输出结果结束。
#include <stdio.h>
#include <stdlib.h>
#define N 13
//计算数组的和
int sum(int coin[],int n){
int result=0;
for(int i = 0;i<n;i++)
result+=coin[i];
return result;
}
//判断2个硬币真假
int judge_1(int coin[]){
int temp=0; //最小值
if(coin[0]<coin[1])
temp=coin[0];
else if(coin[0]>coin[1])
temp=coin[1];
return temp;
}
//判断三个硬币的真假
int judge_2(int coin[]){
int temp=0; //最小值 (假币)
if(coin[0]<coin[1]&&coin[0]<coin[2]){
temp=coin[0];
}
else if(coin[1]<coin[0]&&coin[1]<coin[2]){
temp=coin[1];
}
else if(coin[2]<coin[1]&&coin[2]<coin[1]){
temp=coin[2];
}
return temp;
}
//把硬币分成3份和一个余数组
void allot(int all[],int a[],int b[],int c[],int d[],int n){
printf("\n%d 枚硬币开始分组...\n\n",n);
int j=(n-n%3)/3; //每一碓硬币数
int k=n%3; //余数组
for(int i=0;i<j;i++) //a数组(第一堆)硬币
{
a[i]=all[i];
}
printf("第一堆硬币数量为:%d 枚\n",j);
int w=0;
for(int i=j;i<2*j;i++) //b数组(第二堆) 硬币
{
b[w]=all[i];
w++;
}
printf("第二堆硬币数量为:%d 枚\n",j);
w=0;
for(int i=2*j;i<3*j;i++) //c数组(第三堆) 硬币
{
c[w]=all[i];
w++;
}
printf("第三堆硬币数量为:%d 枚\n",j);
w=0;
if(k!=0){ //余数堆硬币
for(int i=3*j;i<N;i++)
{
d[w]=all[i];
w++;
}
printf("余数堆硬币数量为:%d 枚\n",k);
}
else
{
for(int i=0;i<k;i++)
d[i]=0;
printf("余数堆硬币数量为0枚\n");
}
}
//硬币个数大于3个时进行比较
int compare(int all[],int a[],int b[],int c[],int d[],int n){
int temp=0; //最小值(假币)
allot(all,a,b,c,d,n);
int m= (n-n%3)/3; //前3堆每一堆硬币数量
int u=n%3; //余数堆硬币数量
int sum_1=sum(a,m); //第一堆硬币总质量
printf("第一堆硬币总质量为:%d\n",sum_1);
int sum_2=sum(b,m); //第二堆硬币总质量
printf("第二堆硬币总质量为:%d\n",sum_2);
if(sum_1==sum_2){ //如果a==b
printf("第一堆硬币总质量等于第二堆硬币总质量,则计算第三堆硬币总质量...\n");
int sum_3=sum(c,m); //第三堆硬币总质量
printf("第三堆硬币总质量为:%d\n",sum_3);
if(sum_1==sum_3) // 如果a==c ,假币在d中
{
printf("第一堆硬币总质量等于第三堆硬币总质量,则假币在余数堆中\n");
if(u==1) return d[0];
if(u==2) {temp=judge_1(d);return temp;}
if(u==3) {temp=judge_2(d);return temp;}
if(u>3) {printf("\n");return compare(d,a,b,c,d,u);} //用减治法迭代
}
else{ //如果a不等于c,假币在c中
printf("第一堆硬币总质量不等于第三堆硬币总质量,则假币在第三堆中\n");
if(m==1) return c[0];
if(m==2) {temp=judge_1(c);return temp;}
if(m==3) {temp=judge_2(c);return temp;}
if(m>3) {printf("\n");return compare(c,a,b,c,d,m);}
}
}
else if(sum_1>sum_2){//如果a大于b,假币在b中
printf("第一堆硬币总质量大于第二堆硬币总质量,则假币在第二堆中\n");
if(m==1) return b[0];
if(m==2) {temp=judge_1(b);return temp;}
if(m==3) {temp=judge_2(b);return temp;}
if(m>3) {printf("\n");return compare(b,a,b,c,d,m);} //迭代
}
else {//如果a小于b,假币在a中
printf("第一堆硬币总质量小于第三堆硬币总质量,则假币在第一堆中\n");
if(m==1) return a[0];
if(m==2) {temp=judge_1(a);return temp;}
if(m==3) {temp=judge_2(a);return temp;}
if(m>3) {printf("\n");return compare(a,a,b,c,d,m);} //迭代
}
}
//主函数
int main()
{
int temp=0; //最小值(假币)
int n; //表示硬币个数
int all[N]; //输入的总硬币
int a[15]; //第一堆硬币
int b[15]; //第二堆硬币
int c[15]; //第三堆硬币
int d[15]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //余数堆硬币
printf("请输入硬币个数:");
scanf("%d",&n);
printf("\n请输入硬币重量,真币质量大于假币:\n");
for(int i=0;i<N;i++)
scanf("%d",&all[i]);
printf("\n");
temp=compare(all,a,b,c,d,n);
printf("\n假币是:%d",temp);
return 0;
}
注:更改N可以改变硬币数量,本例以13为例
解题思路2(以12枚硬币为例,且假币未知轻重):
1.将硬币编号:1,2,3,4,5,6,7,8,9,10,11,12。三次称重如下安排:
2.称重:
第一次称重:左盘:1,2,3,4 右盘:5,6,7,8 其他:9,10,11,12
第二次称重:左盘:1,6,7,8 右盘:5,10,11,12 其他:9,2,3,4
第三次称重:左盘:5,6,10,2 右盘:9,7,11,3 其他:1,8,12,4
称重结果:平衡取0,左倾取1,右倾取-1。
3次称重安排可表示成矩阵形式:
11102101310−141005−1−116−1117−11−18−110900−1100−11110−1−1120−10
其中,矩阵第一行是硬币序号,下面每一行都是一次称重结果,1表示该硬币放左盘,-1表示放右盘,0表示不放。矩阵每一列为检测结果,检测结果对应的硬币序号为假币。如果结果与上边的符合,则对应重量为重,如果结果不包含在上述表中,则进行1 -1互换,得到的重量为轻。例如:若称重结果是110,则1号为假币,且重量较重:若称重结果为1-10,1与-1进行交换后为-110,则8号为假币,且重量较轻。
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
/* 1.all[] 3次称重安排的矩阵形式 2.res[] 存放比较结果的数组 */
void compare(int all[],int res[]){
if(all[0]+all[1]+all[2]+all[3]==all[4]+all[5]+all[6]+all[7])
res[0]=0; //天平平衡结果取0
else if(all[0]+all[1]+all[2]+all[3]>all[4]+all[5]+all[6]+all[7])
res[0]=1; //天平左倾结果取1
else
res[0]=-1; //天平左倾结果取-1
if(all[0]+all[5]+all[6]+all[7]==all[4]+all[9]+all[10]+all[11])
res[1]=0;
else if(all[0]+all[5]+all[6]+all[7]>all[4]+all[9]+all[10]+all[11])
res[1]=1;
else
res[1]=-1;
if(all[4]+all[5]+all[9]+all[1]==all[8]+all[6]+all[10]+all[2])
res[2]=0;
else if(all[4]+all[5]+all[9]+all[1]>all[8]+all[6]+all[10]+all[2])
res[2]=1;
else
res[2]=-1;
}
/* 把3次称重安排的矩阵形式展示出来 */
void show(int a[][12]){
for(int i=0;i<3;i++){ //行遍历
for(int j=0;j<12;j++){ //列遍历
printf(" %d ",a[i][j]);
}
printf("\n"); //换行
}
}
/* 1.3次称重安排的矩阵的每一个列为检测结果,检测结果res[] 对应的硬币序号为假币 2.如果结果不包含在上述矩阵中,则1 -1进行互换后再进行寻找 */
int match(int res[],int a[][12]){
switch(res[0]){ //对结果数组第一个数进行判断,然后直接在对应范围进行查找
case 1: // 结果数组第一个数是1
for(int j=0;j<4;j++){
if(res[1]==a[1][j]&&res[2]==a[2][j])
return j; //如果找到和结果一样的列,则返回该列的下标
}
break;
case -1: // 结果数组第一个数是-1
for(int j=4;j<8;j++){
if(res[1]==a[1][j]&&res[2]==a[2][j])
return j;
}
break;
case 0: // 结果数组第一个数是0
for(int j=8;j<12;j++){
if(res[1]==a[1][j]&&res[2]==a[2][j])
return j;
}
break;
default:return -1;
}
return -1; // 如果结果不包含在上述矩阵中,则返回-1
}
/* 如果结果不包含在上述矩阵中,则对res[]中1 -1进行互换 */
void swap(int res[]){
for(int i=0;i<3;i++){
if(1==res[i])
res[i]=-1;
else if(-1==res[i])
res[i]=1;
}
}
/* 1.num_1:用随机函数随机生成一个0~5的数作为真币质量 2.num_2:用随机函数生成的可正可负的数,然后与num_1相加作为假币质量 3.location:随机函数生成的0~12的数,作为假币所在的位置 */
void eval(int all[]) {
int num_1; //真币
int num_2,location; //location为假币位置(0~11)
srand((unsigned)time(NULL));
num_1=rand()%5; //随机生成真币
num_2=rand()%5-3; //num_1+num_2为假币
location=rand()%12; //随机生成假币位置
for(int i=0;i<12;i++) //为12枚硬币赋值
all[i]=num_1;
all[location]=num_1+num_2; //为假币赋值
}
/* 根据match()函数的返回值进行判断 */
void show_res(int a[][12],int res[],int all[],int temp){
if(temp!=-1){ //称重结果在称重安排的矩阵中存在
printf("\n假币是第 %d 枚,假币是:%d",temp+1,all[temp]);
}
else{ //称重结果在称重安排的矩阵中不存在
printf("\n矩阵不存在结果数组序列,进行1 -1交换...\n");
swap(res); //结果进行1 -1交换
printf("\n交换后结果数组是:");
for(int i=0;i<3;i++)
printf("%d ",res[i]);
printf("\n");
temp=match(res,a); //再进行匹配
if(temp!=-1){
printf("\n假币是第 %d 枚,假币是:%d",temp+1,all[temp]);
}
else
printf("\n没有假币!!!");
}
}
int main(){
int temp; //记录match()函数返回的结果,即假币的数组下标
int a[3][12]={1,1,1,1,-1,-1,-1,-1,0,0,0,0,1,0,0,0,-1,1,1,1,0,-1,-1,-1,0,1,-1,0,1,1,-1,0,-1,1,-1,0}; //称重结果矩阵
int all[12]; //存储12个硬币
int result[3]={2,2,2}; //称重结果数组,初始值设为2
printf("\n称重安排矩阵为:\n\n");
show(a); //显示称重矩阵
eval(all); //为all[]赋值
printf("\n12枚硬币的重量:");
for(int i=0;i<12;i++)
printf("%d ",all[i]);
compare(all,result); //进行称重
temp=match(result,a); //结果数组res[] 和矩阵的列进行匹配
printf("\n\n结果数组为:");
for(int i=0;i<3;i++)
printf("%d ",result[i]);
printf("\n");
show_res(a,result,all,temp); //显示最后结果
return 0;
}