算法筆記_004:8枚硬幣問題【減治法】


目錄

1 問題描述

2 解決方案

2.1 減治法原理敘述 

2.2 8枚硬幣規模解法 

2.3 n枚硬幣規模解法 

 


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)比較adcdef的大小。如果abc = def,則假幣必定是g或者h,然后把gh分別與真幣a進行比較大小,從而得到假幣。如果abc > def,則gh必定為真幣,然后比較aeae = a+e)和bdbd = b+d)大小(PS:此處意思為ae = abc - c並把be交換位置,bd = def - f並把eb交換位置),如果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、 算法設計--八枚硬幣問題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM