n枚硬币找出假币问题(包含一枚假币)

问题描述:
在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次称重安排可表示成矩阵形式:

111021013101410051116111711181109001100111101112010 

其中,矩阵第一行是硬币序号,下面每一行都是一次称重结果,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;
}
点赞