1. 程式人生 > >動態規劃演算法-----找零錢問題(求最優解)

動態規劃演算法-----找零錢問題(求最優解)

動態規劃演算法通常用於求解具有某種最優性質的問題。動態規劃演算法與分治法類似,其基本思想都是將待求解問題分解成若干個子問題,先求解子問題,然後從這些子問題的解得到原問題的解。與分治法不同的是,適合於用動態規劃求解的問題,經分解得到的子問題往往不是互相獨立的。如果我們能夠儲存已解決的子問題的答案,而在需要時再找出已求得的答案,這樣就可以避免大量的重複計算,節省時間。我們可以用一個表(備用表)來記錄所有已解的子問題的答案。不管該子問題以後是否被用到,只要它被計算過,就將其結果填入表中。這就是動態規劃法的基本思路。

能採用動態規劃求解的問題的一般要具有3個性質:
(1)最優化原理:如果問題的最優解所包含的子問題的解也是最優的,就稱該問題具有最優子結構,即滿足最優化原理。
(2)無後效性:即某階段狀態一旦確定,就不受這個狀態以後決策的影響。也就是說,某狀態以後的過程不會影響以前的狀態,只與當前狀態有關。
(3)有重疊子問題:即子問題之間是不獨立的,一個子問題在下一階段決策中可能被多次使用到。(該性質並不是動態規劃適用的必要條件,但是如果沒有這條性質,動態規劃演算法同其他演算法相比就不具備優勢)。

動態規劃演算法解決換零錢問題。
現存在一堆面值為 1,2,5,11,20,50 面值的硬幣,問最少需要多少個硬幣才能找出總值為 N個單位的零錢
動態規劃演算法思路:
記d{n}={}表示兌換面值為n的最優解組合為{x1,x2,x3…}
從子問題出發,取0元只有一種方法為{0},即d(0)={0},取1元(0+1)元,最優解為1元+0元,即d(0)+1={0,1}={1};
取2元有兩種方式,即d(0)+2={2}或d(1)+1={1,1},易知最化解為{2}……
以此累推,兌換面值為n的最優解為d(n)=max{d(n-i)+i},其中(i<=n)
當硬幣數量無限時,程式程式碼如下:

public
static void changeCoins(int[] coins,int money) { /*儲存面值為i的紙幣找零所需的最小硬幣數*/ int [] coinsUsed = new int [money+1]; /*硬幣種類數量*/ int valueKinds = coins.length; /*0元的最優解*/ coinsUsed[0] = 0; Map<Integer,HashMap<Integer,Integer>> coinChangeMap = new
HashMap<Integer,HashMap<Integer,Integer>>(); /*從1 - money先求子問題的最優解*/ for(int cents = 1;cents<=money;cents++) { /*當用最小幣值的硬幣找零時,所需硬幣數量最多*/ int minCount = cents; /*儲存各個面值的具體找零方案*/ HashMap<Integer,Integer> minCoinMap = new HashMap<Integer,Integer>(); /*遍歷每一種面值的硬幣,看是否可作為找零的其中之一*/ for(int kind =0;kind<valueKinds;kind++) { /*當前面值*/ int coinVal = coins[kind]; int oppCoinVal = cents - coinVal; /*若當前面值的硬幣小於當前的cents則分解問題並查表*/ if(coinVal <=cents) { int tempCount = coinsUsed[oppCoinVal]+1; if(tempCount<=minCount) { /*子問題的最優解*/ HashMap<Integer,Integer> subMap = coinChangeMap.get(oppCoinVal); HashMap<Integer,Integer> tmpMap = new HashMap<Integer,Integer>(); if(subMap!=null) { /*取到了子問題的最優解*/ tmpMap.putAll(subMap); } /*子問題的最優解+剩下面值 ->整個問題的最優解決*/ if(tmpMap.containsKey(coinVal)) { tmpMap.put(coinVal, subMap.get(coinVal)+1); }else { tmpMap.put(coinVal, 1); } minCount = tempCount; minCoinMap = tmpMap; } } } // 儲存最小硬幣數 coinsUsed[cents] = minCount; coinChangeMap.put(cents, minCoinMap); getArrayItem(coinsUsed); System.out.println("面值為 " + (cents) + " 的最小硬幣數 : " + minCount+",貨幣為"+ minCoinMap); } }
 public static void main(String[] args) {    
            // 硬幣面值預先已經按降序排列    
            int[] coinValue = new int[] { 50, 20, 11, 5, 2,1 };    
            // 需要找零的面值    
            int money = 23;    
            // 儲存每一個面值找零所需的最小硬幣數,0號單元捨棄不用,所以要多加1    
            changeCoins(coinValue,  money);    
        }    

執行截圖:
這裡寫圖片描述