• 问题重述

    设有nn枚硬币,其中仅有一枚假币,在已知或未知假币与真币之间重量关系两种情况下,通过无砝码天平称重的方法鉴别假币,求所需的最少称重次数。

  • 问题分析

    此问题是经典的信息论算法问题,许多大公司都曾以此作为面试、笔试题来考核员工。从信息论角度看,“有nn枚硬币,其中仅有一枚假币”发生概率为P=1nP = \frac{1}{n},“假币与真币之间重量关系未知”发生概率为P=12P = \frac{1}{2},为了确定哪一枚假币,即要消除上述事件的联合不确定性。
    又因为两事件独立,因此有I1=logn+log2=log2n{I_1} = \log n + \log 2 = \log 2n比特;用天平称重,有三种可能:平衡、左倾、右倾,三者等概率,为P=13P = \frac{1}{3},因此天平称重一次消除的不确定性为I2=log3{I_2} = \log 3比特,所以称重次数至少为I1I2=log2nlog3\frac{{{I_1}}}{{{I_2}}} = \frac{{\log 2n}}{{\log 3}}次。

  • 解题思路

    【问题一】当n=12n = 12 时
    将12个硬币编号为:1,2,3,4,5,6,7,8,9,10,11,12
    称重安排如下表:

    称重结果表示为:0:平衡 1:左倾 2:右倾
    可以得到如下表结果:

    同理可用矩阵表示3次称重的安排,矩阵上方为硬币序号,矩阵的行为3次称重时矩阵的放置位置,1表示放到左盘,2表示放到右盘,0表示不参与称重。

    123456789101112⎛⎝⎜110101102100221211212210002021022020⎞⎠⎟

    \begin{array}{l}{\kern 10pt}1{\kern 11pt} 2{\kern 10pt} 3{\kern 10pt} 4{\kern 10pt} 5{\kern 10pt}6{\kern 10pt}7{\kern 11pt}8{\kern 10pt}9{\kern 7pt} 10{\kern 6pt} 11{\kern 5pt} 12\\ \left( {\begin{array}{*{20}{l}} 1&1&1&1&2&2&2&2&0&0&0&0\\ 1&0&0&0&2&1&1&1&0&2&2&2\\ 0&1&2&0&1&1&2&0&2&1&2&0 \end{array}} \right) \end{array}
    由表格与矩阵,发现:如果检测结果与矩阵的某列符合,则对应序号的硬币即为假币,且重量较重;如果检测结果不在上述矩阵的列中,将1、2互换,得到假币对应序号,重量较轻。例如,若称重结果为110,则1号为假币,且重量较重;若称重结果为201,将1与2互换,得到102,则3号为假币,且重量较轻。

    【问题二】当n=39n{\rm{ = }}39时
    查阅资料可得,kk次称重最多可以在3k−32\frac{{{3^k} - 3}}{2}个硬币中找到不同的硬币,并判断其轻重。已知硬币数量为39,可求得需要称量的次数k=5k{\rm{ = }}5

    1. 编码
      以称量次数为编码长度,使用0、1、2排列组合进行编码,再去掉全为0、全为1和全为2,可知一共有3k−3{3^k} - 3个编码。在一个编码中,第一处相邻数字不同的情况是01、12或20,则我们称它为正序码,如11010; 否则为逆序码,如11210;在长度为 的编码中,正序码和逆序码的数量相等,为3k−32\frac{{{3^k} - 3}}{2}个。
    2. 赋值
      如果把一个正序码的0换成1,1换成2,2换成0,则它认为正序码。由此,将正序码每3个分为一组,例如11010、22121、00202。7将正序码的0与2互换,即可得到一个逆序码,因此每枚硬币均有一个正序码一个逆序码。
    3. 称重
      第一次,将正序码第一位为1的硬币放在左侧,为2的硬币放在右侧,其余不参与称重,如果天平平衡记为0,左倾记为1,右倾记为2;
      第二次,将正序码第二位为1的硬币放在左侧,为2的硬币放在右侧,其余不参与称重;
      每轮如此,重复 次,结果得到一个 位编码。如果此编码为某个硬币的正序码,则这个硬币比其余硬币重;如果此编码为某个硬币的逆序码,则这个硬币比其余硬币轻。
  • 可视化演示
    以n=12n=12为例
    (1)编号(假设6号硬币为假币)

    (2)赋值

    123456789101112⎛⎝⎜110101102100221211212210002021022020⎞⎠⎟

    \begin{array}{l}{\kern 10pt}1{\kern 11pt} 2{\kern 10pt} 3{\kern 10pt} 4{\kern 10pt} 5{\kern 10pt}6{\kern 10pt}7{\kern 11pt}8{\kern 10pt}9{\kern 7pt} 10{\kern 6pt} 11{\kern 5pt} 12\\ \left( {\begin{array}{*{20}{l}} 1&1&1&1&2&2&2&2&0&0&0&0\\ 1&0&0&0&2&1&1&1&0&2&2&2\\ 0&1&2&0&1&1&2&0&2&1&2&0 \end{array}} \right) \end{array}
    (3)称重
    将正序码第一位为1的硬币放在天平的左侧,为2的放在右侧,为0的放在旁边:

    将正序码第二位为1的硬币放在天平的左侧,为2的放在右侧,为0的放在旁边:

    将正序码第三位为1的硬币放在天平的左侧,为2的放在右侧,为0的放在旁边:

    结果与事先挑选的6号硬币一致

  • 附录
    称球通解问题的证明
    摘自The Problem of the Pennies, F. J. Dyson, The Mathematical Gazette , Vol. 30, No. 291 (Oct., 1946), pp. 231-234

引理1:在多于2个的一堆球,已知次品在其中,称k次可以并最多在3^k个半确定的球中找出次品,并且知道其轻重。
用数学归纳法,当k=1k=1。3个半确定的球,一定至少有两个属于同一类,比如说疑重球,将这两个上天平,重的那个就是次品,如果平衡,外边的那个就是次品,而且从它的类别知道这次品是较重还是较轻。验证正确。
假设结论对k−1k-1次正确。将不多于3k3^k个半确定的球三等分,如果不能够等分,除天平两边要等数外,三方都不多于3(k−1)3^(k-1)个球,且使得两边共有偶数个疑重球,记为2a2a个。这总是可以做到的。因为我们可以把天平上“不齐整”的球和外面异类的球对调。这样天平左右各有aa个疑重球和3k−1−a3^{k-1}-a个疑轻球。这一般有多种可能的aa值满足要求,任取一个都行。这时如果左边重,左边的aa个疑重球和右边的3k−1−a3^{k-1}-a个疑轻球,共3k−13^{k-1}个半确定球有嫌疑,其他都是正品。如果右边重,同理将嫌疑缩小到3k−13^{k-1}个半确定球。如果平衡,嫌疑在外面的3k−13^{k-1}个半确定球中。如果这嫌疑是1个或2个半确定球,可以用一个正品与其中一个称一次解决,其他情况我们已知用k−1k-1次可以解决不多于3k−13^{k-1}个半确定的球。证毕。
引理2:已知次品在其中,加一个已知的正品球称k次,可以并最多在3k+12\frac{3^{k+1}}{2}个球中找出次品,但有且仅有一种情况不知其次品的轻重。
在k=1k=1情况,有2个球,取一个与正品球上天平,如果平衡,次品在外面,但不知它比正品轻还是重,注意这是归纳证明中仅有的情况。如果只有1个球,它就是次品了,称一次可以知道比正品轻了还是重。
假设结论对k−1k-1次正确。考虑第一次天平称量,一边取3k−1−12\frac{3^{k-1}-1}{2}个加上一个正品球,另一边取3k−1−12\frac{3^{k-1}-1}{2}个球。我们知道这次称量以后,如果天平平衡,那么嫌疑在外面。余下k−1k-1次可以解决这里的不超过3k−1−12\frac{3^{k-1}-1}{2}个球,有且仅有一种情况不知其次品的轻重。如果天平不平衡,天平的两边都是半确定的球。由引理1知道,余下k−1k-1次可以解决这里的3k−13^{k-1}个球。因为这个数是奇数,所以我们必须在第一次天平称量时再加上一个已知的正品球。因此称kk次,可以并最多解决3k+12\frac{{{3^{k + 1}}}}{2}个球。证毕。
定理:在一堆等重球中有一个重量不同的次品球,用天平称kk次找出来,这堆球最多且可以是3k−12\frac{3^{k-1}}{2}个球。
在第一次称量我们最多可以将3k−1−13^{k-1}-1个球两等分放在天平上,如果不平衡,由引理1,可以再称k−1k-1次解决。如果平衡,天平这里都是已知球,由引理2,可以再称k−1k-1次解决外面的3k−1+12\frac{3^{k-1}+1}{2}个球。所以总共可以解决3k−12\frac{3^{k-1}}{2}个球。证毕。

  • 程序代码
package com.yrwan.findCoin;import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;public class Main {static int round = 1;  static int maxSteps;  public static void run(Status root, List<Status> list) {  //求解 List<Status> newlist = new ArrayList<Status>();   for (int i=0; i<list.size(); i++) {  Status status = list.get(i);  status.produceBalances();  for (int j=0; j<status.bls.size(); j++) {  Balance bl = status.bls.get(j);  bl.weight();  if (root.succeed()) {  return;  }  if (bl.out1.isUnknown()) newlist.add(bl.out1);  if (bl.out2.isUnknown()) newlist.add(bl.out2);  if (bl.out3.isUnknown()) newlist.add(bl.out3);  }  }  round++;  run(root, newlist);   }  public static void print(Status st, int depth) { //输出结果  String indent="";  for (int i=0; i<depth-1; i++) indent = indent+"t";   Balance bl=null;  for (int i=0; i<st.bls.size(); i++)   if (st.bls.get(i).unresolved==0) bl=st.bls.get(i);  if (bl!=null) {  if (depth>maxSteps) maxSteps=depth;  System.out.println(indent + "第" + depth + "步称重: " + bl + "rn");  System.out.println(indent + "如果一样重: " + bl.out1 + (bl.out1.getConclusion()==Status.RESOLVED?"  *解决*":(bl.out1.getConclusion()==Status.REDICULOUS?"  ×不可能×":"")) + "rn");  print(bl.out1, depth+1);  System.out.println(indent + "如果左边重: " + bl.out2 + (bl.out2.getConclusion()==Status.RESOLVED?"  *解决*":(bl.out2.getConclusion()==Status.REDICULOUS?"  ×不可能×":"")) + "rn");  print(bl.out2, depth+1);  System.out.println(indent + "如果右边重: " + bl.out3 + (bl.out3.getConclusion()==Status.RESOLVED?"  *解决*":(bl.out3.getConclusion()==Status.REDICULOUS?"  ×不可能×":"")) + "rn");  print(bl.out3, depth+1);  }  }  public static void main(String[] args) {Scanner sc = new Scanner(System.in);  System.out.println("请输入硬币个数:");  int n = sc.nextInt();  sc.close();Status root = new Status(n);  ArrayList<Status> list = new ArrayList<Status>();  list.add(root);  run(root, list);  System.out.println("***** 求解步骤*****");  maxSteps = 0;  print(root, 1);  System.out.println("***** 最少" + maxSteps + "步可找出假币*****");  }
}
package com.yrwan.findCoin;public class Balance {public int[] data;  public Status in,out1,out2,out3;   public int unresolved = 3;  public Balance(int[] data) {  this.data = data.clone();  }  public void weight() {//称重量,推理出三种可能的结果   int[] temp;  // 一样重   temp = in.data.clone();  for (int i=1; i<4; i++) { //所有参与称重的硬币都移入正常硬币集合   temp[0] = temp[0] + data[i] + data[i+4];  temp[i] = temp[i] - data[i] - data[i+4];  }  out1 = new Status(temp);  out1.addParent(this);  //左边重   temp = in.data.clone();  for (int i=1; i<4; i++) {  temp[0] = temp[0] + temp[i] - data[i] - data[i+4]; //未参与称重的硬币  -->> 正常硬币   }  temp[0] += data[3] + data[6]; //左边的疑似轻硬币、右边的疑似重硬币  -->> 正常硬币   temp[1] = 0;  temp[2] = data[1] + data[2]; //左边的不明轻重硬币移入疑似重硬币集合   temp[3] = data[5] + data[7]; //右边的不明轻重硬币移入疑似轻硬币集合   out2 = new Status(temp);  out2.addParent(this);  //右边重   temp = in.data.clone();  for (int i=1; i<4; i++) {  temp[0] = temp[0] + temp[i] - data[i] - data[i+4]; //未参与称重的硬币  -->> 正常硬币   }  temp[0] += data[2] + data[7]; //左边的疑似重硬币、右边的疑似轻硬币  -->> 正常硬币   temp[1] = 0;  temp[2] = data[5] + data[6]; //右边的不明轻重硬币移入疑似重硬币集合   temp[3] = data[1] + data[3]; //左边的不明轻重硬币移入疑似轻硬币集合   out3 = new Status(temp);  out3.addParent(this);  }  public String toString(){  return "(" + (data[0]>0?"正常硬币×"+data[0]+"个 ":"") + (data[1]>0?"不明硬币×"+data[1]+"个 ":"")   +(data[2]>0?"疑似重硬币×"+data[2]+"个 ":"") + (data[3]>0?"疑似轻硬币×"+data[3]+"个 ":"")      + ") --天平-- ("  + (data[4]>0?"正常硬币×"+data[4]+"个 ":"") + (data[5]>0?"不明硬币×"+data[5]+"个 ":"")   +(data[6]>0?"疑似重硬币×"+data[6]+"个 ":"") + (data[7]>0?"疑似轻硬币×"+data[7]+"个 ":"") + ")";     }  public void prop() {  if (unresolved <= 0) return;  unresolved--;  if (unresolved == 0) in.setConclusion(Status.RESOLVABLE);  }
}
package com.yrwan.findCoin;import java.util.ArrayList;
import java.util.List;public class Status {public static int RESOLVED=1, UNKNOWN=2, REDICULOUS=3, RESOLVABLE=4;  public int count=0;  public int[] data;  public List<Balance> parents = new ArrayList<Balance>();  public List<Balance> bls = new ArrayList<Balance>();  private int conclusion;  public Status(int c) {  count = c;  int[] data1 = {0,c,0,0};  data = data1;  int conc = data[0]<count-1?UNKNOWN:(data[0]==count-1?RESOLVED:REDICULOUS);  setConclusion(conc);  }  public Status(int[] is) {  data = is;  for (int i=0; i<is.length; i++) count+=is[i];  int conc = data[0]<count-1?UNKNOWN:(data[0]==count-1?RESOLVED:REDICULOUS);  setConclusion(conc);  }  public void addParent(Balance bl) {  parents.add(bl);  if (conclusion==RESOLVED || conclusion==RESOLVABLE || conclusion==REDICULOUS) bl.prop();  }  public String toString() {  return "正常" + data[0] + "、不明" + data[1] + "、或重" + data[2] + "、或轻" + data[3];  }  public void setConclusion(int conc) {  if (conclusion == conc) return;  conclusion = conc;  if (conclusion==RESOLVED || conclusion==RESOLVABLE || conclusion==REDICULOUS)   for (int i=0; i<parents.size(); i++)  parents.get(i).prop();  }  public int getConclusion() {return conclusion;}  public boolean succeed() {return conclusion==RESOLVED || conclusion==RESOLVABLE;}  public boolean isUnknown(){return conclusion==UNKNOWN;}  public void produceBalances() {//得到当前状况下所有可能的称重方案   List<int[]> bldata = getBalanceDataArray(data);  bls = new ArrayList<Balance>();  for (int i=0; i<bldata.size(); i++) {  Balance bl = new Balance(bldata.get(i));  bl.in = this;  bls.add(bl);  }  }  private List<int[]> getBalanceDataArray(int[] src) {  List<int[]> list = new ArrayList<int[]>();  list.add(new int[src.length*2]);  return getBalanceDataArray(src,0,list);  }  private List<int[]> getBalanceDataArray(int[] src, int id, List<int[]> list) {  int total=0,left,right;  if (id>=src.length) {  for (int i=list.size()-1; i>=0; i--) {  int[] is = list.get(i);  left=0;  right=0;  for (int j=0; j<src.length; j++) left+=is[j];  for (int j=src.length; j<src.length*2; j++) right+=is[j];  if (left!=right || left==0 || is[0]>0&&is[is.length/2]>0)  list.remove(i);  }  return list;  }  List<int[]> r = new ArrayList<int[]>();  for (int i=0; i<src.length; i++) total += src[i];  int half = total/2;  for (int i=0; i<list.size(); i++) {  int[] is = list.get(i);  left=0;  right=0;  for (int j=0; j<src.length; j++) left+=is[j];  for (int j=src.length; j<src.length*2; j++) right+=is[j];  for (int j=0; j<=Math.min(half-left, src[id]); j++) {  for (int k=0; k<=Math.min(half-right, src[id]-j); k++) {  int[] iis = list.get(i).clone();  iis[id] = j;  iis[id+src.length] = k;  r.add(iis);  }  }  }  return getBalanceDataArray(src,id+1,r);  }
}

一般性假币称重鉴别问题相关推荐

  1. 信息熵--硬币称重问题-详解

    在补<信息熵基础>,此书理论清晰,后面还有一堆要命的习题,加深理解和应用,实乃佳作.其中一些题目对我来说确实不易,记录巩固一下,也和大家交流一下. 顺便补一下,一份完整的参考资料或者书籍, ...

  2. 12枚硬币称重问题(面试)

    问题描述: 12枚硬币,其中11枚真币1枚假币,现有一架天平,最少称多少次可以找出这枚假币并且知道假币和真币的相对重量. 答案是三次,称重过程描述如下. 第一步:分组,分三组,1 2 3 4为一组,5 ...

  3. 关于stm32f407wifi模块的设置_料粉定量称重模块,罐子称重传感器

    料粉定量称重模块,罐子称重传感器 上海恒刚生产销售的称重模块采用了的传感器元件,其特点是结构紧凑,不需安装其他配件,自稳定的传感器承压头使计量,重复性好:高速简便的安装,节约安装和停机维修时间,称重模 ...

  4. 局域网通讯工具_自动称重带无线通讯WIFI传输功能设备

    自动称重带无线通讯WIFI传输功能设备详情内容/ Content details 支持: 远程数据库功能,支持OPCUA协议,能实时上传各种称重信息:通过SQLServer等实现称重数据与记录的上传到 ...

  5. pandas重命名列名称、数据列名称重命名(Rename Column Names): rename、set_axis、df.columns

    pandas重命名列名称.数据列名称重命名(Rename Column Names): rename.set_axis.df.columns 目录 pandas重命名列名称.数据列名称重命名(Rena ...

  6. 电子称重管理计算机,称重管理系统

    霄博称重软件主要是针对企业在原料采购.成品销售及厂内物资倒运.物资调拨过程进行的计量管理:霄博称重单机软件适用于单台磅,管理相对简单的企业的计量管理. 1.计量功能 霄博单机版称重软件的计量过程可以按 ...

  7. 做弱电机房工程的时候,如何解决机房称重问题?

    我们在做一些机房工程的时候经常遇到机房承重的问题,尤其在一些比较高的楼层上面做机房是不是合适呢?机房称重符合要求吗?今天我们一起来探讨一下. 一,什么是机房承重? 众所周知,机房是电子设备运行的场所, ...

  8. 判断文件是否损坏_称重传感器好坏的判断方法,看完秒懂!

    完整的称重系统通常由称重传感器和称重显示器(即称重仪器)组成,下面将对其进行详细介绍. 一:称重传感器有四线制和六线制称重传感器.四线制: EXC +,EXC-:电源线,即仪器到传感器的直流电源. S ...

  9. 地磅称重软件源码_【漯河衡器】浅谈地磅称重的发展趋势

    发展,是人类物质社会的一个永恒的话题.每一个个体,集团,企业都需要不断地发展.才能应对社会的整体发展.对于地磅称重行业也是如此.今天衡安软件的小编就来讲一讲地磅称重的发展以及发展趋势. 地磅称重 一. ...

最新文章

  1. CSP认证201412-1 门禁系统[C++题解]:哈希表
  2. GooglePR说明
  3. 微服务架构设计模式~根据业务能力进行服务拆分
  4. java 合并对象中属性_Java2个对象形集合按某一个属性合并
  5. 阶段3 3.SpringMVC·_04.SpringMVC返回值类型及响应数据类型_8 响应json数据之响应json格式数据...
  6. 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_01 File类_8_File类遍历(文件夹)目录功能...
  7. java 工作流 实例_Activiti工作流的应用示例
  8. 教你编写第一个人工智能程序
  9. 万字长文丨大白话带你由浅入深Python编程语言
  10. 图像小波去噪matlab程序,小波去噪程序(用matlab实现)
  11. 海外侨胞代表建议广东各市抱团出海开设名优特产品实体店
  12. 双十一,稳!2小时1000亿,阿里的技术露大脸了
  13. 问题 D: 上帝视角
  14. 最新小程序授权+php后端(附demo源码)
  15. MS08-067远程代码执行漏洞(CVE-2008-4250) | Windows Server服务RPC请求缓冲区溢出漏洞复现
  16. 山东如意路嘉纳高级定制西装品牌惊艳亮相intertextile面料展 - 服装资讯中心 - 华衣网...
  17. cocos tween
  18. 8月TIOBE编程语言排行榜出炉:它稳了!
  19. Python爬虫学习笔记-第十一课(selenium下)
  20. 按头安利 好看又实用的公文包 文件包3d模型素材看这里

热门文章

  1. Git之多人协同开发
  2. stream.map 和 stream.foreach 的区别
  3. 联想Idea Pad Y430 开启VT
  4. Winform知识扩展-------右键菜单与InputBox弹出式输入框
  5. 2021年R1快开门式压力容器操作最新解析及R1快开门式压力容器操作操作证考试
  6. 电视剧也能训练人工智能?爱丁堡大学对此进行了实验 | 研究
  7. Ubuntu下android-4.0.3_r1源码下载,阅读工具安装配置,源码编译详解
  8. multiple 属性,上传多个文件或图片
  9. 2011 模拟 c语言 本科(含答案)(第二届“国信蓝点杯”全国软件专业人才设计与开发大赛)...
  10. 卡耐基-----人性的弱点