目錄
1 問題描述
(1)實驗題目
在8枚外觀相同的硬幣中,有一枚是假幣,並且已知假幣與真幣的重量不同,但不知道假幣與真幣相比較輕還是較重。可以通過一架天平來任意比較兩組硬幣,設計一個高效的算法來檢測這枚假幣。
(2)實驗目的
1)深刻理解並掌握減治法的設計思想並理解它與分治法的區別;
2)提高應用減治法設計算法的技能。
3)理解這樣一個觀點:建立正確的模型對於問題的求解是非常重要的。
(3)實驗要求
1)設計減治算法實現8枚硬幣問題;
2)設計實驗程序,考察用減治技術設計的算法是否高效;
3)擴展算法,使之能處理n枚硬幣中有一枚假幣的問題。
(4)實現提示
假設用一個數組B[n]表示硬幣,元素B[i]中存放第i枚硬幣的重量,其中n-1個元素的值都是相同的,只有一個元素與其他元素值不同,則當n=8時即代表8枚硬幣問題。由於8枚硬幣問題限制只允許使用天平比較輕重,所以,算法中只能出現元素相加和比較的語句。
2 解決方案
2.1 減治法原理敘述
在說減法法原理之前,我們先來簡單看看分治法原理:分治法是把一個大問題划分為若干子問題,分別求解子問題,然后再把子問題的解進行合並得到原問題的解。
而減治法同樣是把大問題分解成為若干個子問題,但是這些子問題不需要分別求解,只需求解其中的一個子問題,也無需對子問題進行合並。換種說法,可以說減治法是退化的分治法。
減治法原理正式描述:減治法(reduce and conquer method)將原問題的解分解為若干個子問題,並且原問題的解與子問題的解之間存在某種確定關系,如果原問題的規模為n,則子問題的規模通常是n/2 或n-1。
2.2 8枚硬幣規模解法
求解思路:
(1)首先輸入8枚硬幣重量,存放在一個長度為8的一維數組中。
(2)定義a,b,c,d,e,f,g,h八個變量,分別對應一枚硬幣的重量。然后把這8枚硬幣分成三組,分別為abc(abc = a+b+c)、def(def = d+e+f)、gh。
(3)比較adc和def的大小。如果abc = def,則假幣必定是g或者h,然后把g和h分別與真幣a進行比較大小,從而得到假幣。如果abc > def,則g和h必定為真幣,然后比較ae(ae = a+e)和bd(bd = b+d)大小(PS:此處意思為ae = abc - c並把b和e交換位置,bd = def - f並把e和b交換位置),如果ae = bd,則假幣必定是c或者f,然后依次與g比較,從而得到假幣;如果ae > bd,則假幣必定是a或者d,然后依次與g比較,從而得到假幣;如果ae < bd,則假幣必定是e或者b,然后依次與g比較,從而得到假幣。
(4)abc < def情況參照(3)中思想求解,最終得到假幣。
具體程序流程圖如圖1所示:
圖1 8枚硬幣問題規模程序流程圖
具體代碼如下:
package com.liuzhen.coin; import java.util.*; public class EightCoins { public static void printFakeCoin(int [] A){ int a,b,c,d,e,f,g,h; //八枚硬幣重量 a = A[0]; b = A[1]; c = A[2]; d = A[3]; e = A[4]; f = A[5]; g = A[6]; h = A[7]; int abc = a+b+c; int def = d+e+f; //當abc重量大於def重量時,找出其中假幣,並打印輸出 if(abc > def){ if(a+e > b+d){ //此時,假幣必定為a或者d if(a > g) System.out.println("假幣為第1枚硬幣,較重,重量為:"+a); else{ if(a < g) System.out.println("假幣為第1枚硬幣,較輕,重量為:"+a); else{ int test = d-g; if(test > 0) System.out.println("假幣為第4枚硬幣,較重,重量為:"+d); else System.out.println("假幣為第4枚硬幣,較輕,重量為:"+d); } } } if(a+e == b+d){ //此時,假幣必定為c或者f if(c > g) System.out.println("假幣為第3枚硬幣,較重,重量為:"+c); else{ if(c < g) System.out.println("假幣為第3枚硬幣,較輕,重量為:"+c); else{ int test = f-g; if(test > 0) System.out.println("假幣為第6枚硬幣,較重,重量為:"+f); else System.out.println("假幣為第6枚硬幣,較輕,重量為:"+f); } } } if(a+e < b+d){ //此時,假幣必定為b或者e if(b > g) System.out.println("假幣為第2枚硬幣,較重,重量為:"+b); else{ if(c < g) System.out.println("假幣為第2枚硬幣,較輕,重量為:"+b); else{ int test = e-g; if(test > 0) System.out.println("假幣為第5枚硬幣,較重,重量為:"+e); else System.out.println("假幣為第5枚硬幣,較輕,重量為:"+e); } } } } //當abc重量等於def重量時,則假幣必定為g或者h if(abc == def){ if(g > a) System.out.println("假幣為第7枚硬幣,較重,重量為:"+g); else{ if(g < a) System.out.println("假幣為第7枚硬幣,較輕,重量為:"+g); else{ int test = h-a; if(test > 0) System.out.println("假幣為第8枚硬幣,較重,重量為:"+h); else System.out.println("假幣為第8枚硬幣,較輕,重量為:"+h); } } } //當abc重量小於def重量時,找出其中假幣,並打印輸出 if(abc < def){ if(a+e > b+d){ //此時,假幣必定為b或者e if(b > g) System.out.println("假幣為第2枚硬幣,較重,重量為:"+b); else{ if(b < g) System.out.println("假幣為第2枚硬幣,較輕,重量為:"+b); else{ int test = e-g; if(test > 0) System.out.println("假幣為第5枚硬幣,較重,重量為:"+e); else System.out.println("假幣為第5枚硬幣,較輕,重量為:"+e); } } } if(a+e == b+d){ //此時,假幣必定為c或者f if(c > g) System.out.println("假幣為第3枚硬幣,較重,重量為:"+c); else{ if(c < g) System.out.println("假幣為第3枚硬幣,較輕,重量為:"+c); else{ int test = f-g; if(test > 0) System.out.println("假幣為第6枚硬幣,較重,重量為:"+f); else System.out.println("假幣為第6枚硬幣,較輕,重量為:"+f); } } } if(a+e < b+d){ //此時,假幣必定為a或者d if(a > g) System.out.println("假幣為第1枚硬幣,較重,重量為:"+a); else{ if(a < g) System.out.println("假幣為第1枚硬幣,較輕,重量為:"+a); else{ int test = d-g; if(test > 0) System.out.println("假幣為第4枚硬幣,較重,重量為:"+d); else System.out.println("假幣為第4枚硬幣,較輕,重量為:"+d); } } } } } public static void main(String args[]){ Scanner scan = new Scanner(System.in); int[] weightCoin = new int[8]; System.out.println("請您輸入8枚硬幣的重量(其中有一枚假幣,其它硬幣重量均相同):"); for(int i = 0; i < 8; i++) weightCoin[i] = scan.nextInt(); printFakeCoin(weightCoin); } }
運行結果截圖如圖2所示:
圖2 8枚硬幣問題運行結果截圖
2.3 n枚硬幣規模解法
求解思路:
此處我寫了兩個方法:
方法1:
/*返回一個長度為3的一維數組,result[0]表示假幣輕重,值為0表示偏輕,值為1表示偏重;result[1]表示一枚真幣的重量; result[2]表示,當硬幣個數為奇數且最后一枚為假幣時,把這枚假幣重量賦值給result[2],否則result[2]值為0*/ public static int[] getJudgeCoinArray(int[] A);
方法2:
/*返回一個長度為3的一維數組,result[0]表示假幣輕重,值為0表示偏輕,值為1表示偏重;result[1]表示假幣的重量; result[2]表示假幣在硬幣數組中的具體位置*/ public static int[] getFakeCoin(int[] A,int min,int max,int judge,int real);
具體程序流程圖如圖3所示(PS:此處圖畫的不完整,主要是表達程序的思想,不要糾結喲):
圖3 n枚硬幣問題規模程序流程圖
具體代碼如下:
package com.liuzhen.coin; import java.util.Scanner; public class NCoins { /*返回一個長度為3的一維數組,result[0]表示假幣輕重,值為0表示偏輕,值為1表示偏重;result[1]表示假幣的重量; result[2]表示假幣在硬幣數組中的具體位置*/ public static int[] getFakeCoin(int[] A,int min,int max,int judge,int real){ int[] result = new int[3]; //定義一個長度為3的一維數組,初始化所有值為0 if((max-min)%2 == 1){ //當max-min為奇數時,不能完成二分,判斷A[max-1]是否為假幣,若是,則直接返回結果,否則執行max= max-1 if(A[max-1] != real){ result[0] = judge; result[1] = A[max-1]; result[2] = max; return result; } max = max-1; } if(max-min == 2){ //當max-min為2時,此時只剩下兩枚硬幣,可以直接比較,找出假幣 int a = A[min]-real; int b = A[max-1]-real; if(a != 0){ result[0] = judge; result[1] = A[min]; result[2] = min+1; } if(b != 0){ result[0] = judge; result[1] = A[max-1]; result[2] = max; } return result; } int sum1 = 0,sum2 = 0; // System.out.println("max-min值為:"+(max-min)); // System.out.println("judge值為:"+judge); for(int i = 0;i<(max-min)/2;i++){ sum1 += A[min+i]; //二分后的左半部分硬幣總重量 sum2 += A[(max+min)/2+i]; //二分后的右半部分硬幣總重量 } // System.out.println("sum1值為:"+sum1); // System.out.println("sum2值為:"+sum2); //假幣較重 if(judge == 1){ if(sum1 > sum2) max = (max+min)/2; //此時假幣在左半部分 else min = (max+min)/2; //此時假幣在右半部分 } //假幣較輕 if(judge == 0){ if(sum1 > sum2) min = (max+min)/2; //此時假幣在右半部分 else max = (max+min)/2; //此時假幣在左半部分 } // System.out.println("min值為:"+min); // System.out.println("max值為:"+max); result = getFakeCoin(A,min,max,judge,real); //遞歸求解最終假幣結果 return result; } /*返回一個長度為3的一維數組,result[0]表示假幣輕重,值為0表示偏輕,值為1表示偏重;result[1]表示一枚真幣的重量; result[2]表示,當硬幣個數為奇數且最后一枚為假幣時,把這枚假幣重量賦值給result[2],否則result[2]值為0*/ public static int[] getJudgeCoinArray(int[] A){ int[] result = new int[3]; //定義一個長度為3的一維數組,初始化所有值為0 int len = A.length; //獲取數組A的長度,即硬幣的總個數 int a = A[len-1]; //最后一枚硬幣重量,用於判斷當硬幣個數為奇數時,最后一枚硬幣時假幣的情況 if(len%2 == 1){ //當硬幣總個數為奇數時,將硬幣總個數減1,變成偶數 len = len-1; } int[] Left1 = new int[len/2]; //二分左半部分 int[] Right1 = new int[len/2]; //二分右半部分 int sum1 = 0,sum2 = 0; for(int i = 0;i<len/2;i++){ Left1[i] = A[i]; Right1[i] = A[len/2+i]; sum1 += A[i]; //左半部分硬幣總重量 sum2 += A[len/2+i]; //右半部分硬幣總重量 } // System.out.println("sum1值為:"+sum1); // System.out.println("sum2值為:"+sum2); int sum3=0,sum4=0; int len1 = Left1.length; //獲取二分后左邊數組長度 int b = Left1[len1-1]; //當Left1數組長度為奇數時,用於判斷最后一枚硬幣是否為假幣 if(len1%2 == 1) //當len1為奇數時,將len1減1,變成偶數 len1 = len1-1; for(int j = 0;j<len1/2;j++){ sum3 += Left1[j]; //左半部分的左半部分總重量 sum4 += Left1[len1/2+j]; //左半部分的右半部分總重量 } //當左半部分硬幣重量大於右半部分重量時 if(sum1 > sum2){ if(sum3 == sum4){ if(b > Left1[0]){ //此時可判斷b為假幣,且較重 result[0] = 1; result[1] = Left1[0]; result[2] = b; return result; } if(b < Left1[0]){ //此時可判斷b為假幣,且較輕 result[0] = 0; result[1] = Left1[0]; result[2] = b; return result; } //否則,假幣在Right1中,且較輕 result[0] = 0; result[1] = Left1[0]; } if(sum3 != sum4){ //假幣在Left1中,且較重 result[0] = 1; result[1] = Right1[0]; } } //當左半部分硬幣重量小於右半部分重量時 if(sum1 < sum2){ if(sum3 == sum4){ if(b > Left1[0]){ //此時可判斷b為假幣,且較重 result[0] = 1; result[1] = Left1[0]; result[2] = b; return result; } if(b < Left1[0]){ //此時可判斷b為假幣,且較輕 result[0] = 0; result[1] = Left1[0]; result[2] = b; return result; } //否則,假幣在Right1中,且較重 result[0] = 1; result[1] = Left1[0]; } if(sum3 != sum4){ //假幣在Left1中,且較輕 result[0] = 0; result[1] = Right1[0]; } } //當左半部分硬幣重量等於右半部分重量時 if(sum1 == sum2){ if(a > Left1[0]){ //此時可判斷a為假幣,且較重 result[0] = 1; result[1] = Left1[0]; result[2] = a; return result; } if(a < Left1[0]){ //此時可判斷a為假幣,且較輕 result[0] = 0; result[1] = Left1[0]; result[2] = a; return result; } } // System.out.println("sum3值為:"+sum3); // System.out.println("sum4值為:"+sum4); return result; } public static void main(String args[]){ Scanner sc = new Scanner(System.in); System.out.println("請您輸入n枚硬幣的總個數:"); int n = sc.nextInt(); int[] A = new int[n]; System.out.println("請您輸入n枚硬幣的重量(其中有一枚假幣,其它硬幣重量均相同):"); for(int i = 0; i < n; i++) A[i] = sc.nextInt(); int[] result1 = getJudgeCoinArray(A); int len = A.length; //數組A的長度 int judge = result1[0]; //假幣輕重判斷 int real = result1[1]; //真幣重量 int fakeCoin = result1[2]; //假幣重量 System.out.println("硬幣總個數為:"+len); System.out.println("judge值為:"+judge+"(1表示假幣較重,0表示假幣較輕)"); System.out.println("真幣重量為:"+real); if(fakeCoin != 0) System.out.println("假幣重量為:"+fakeCoin); else{ int[] result = getFakeCoin(A,0,len,judge,real); if(result[0] == 1) System.out.print("假幣較重,"); else System.out.print("假幣較輕,"); System.out.print("且假幣是第"+result[2]+"硬幣,"); System.out.println("假幣重量為:"+result[1]); } } }
運行結果截圖如圖4所示:
圖4 n枚硬幣問題規模運行結果截圖
參考資料:
1、(五)減治法
2、 算法設計--八枚硬幣問題