動態規劃求解硬幣找零問題——Java實現

來源:互聯網
上載者:User

動態規劃的基本思想是將待求解問題分解成若干個子問題,先求解子問題,並將這些子問題的解儲存起來,如果以後在求解較大子問題的時候需要用到這些子問題的解,就可以直接取出這些已經計算過的解而免去重複運算。儲存子問題的解可以使用填表方式,例如儲存在數組中。

 動態規劃的主要痛點在於理論上的設計,也就是上面4個步驟的確定,一旦設計完成,實現部分就會非常簡單。使用動態規劃求解問題,最重要的就是確定動態規劃三要素:問題的階段,每個階段的狀態以及從前一個階段轉化到後一個階段之間的遞推關係。遞推關係必須是從次小的問題開始到較大的問題之間的轉化,從這個角度來說,動態規劃往往可以用遞迴程式來實現,不過因為遞推可以充分利用前面儲存的子問題的解來減少重複計算,所以對於大規模問題來說,有遞迴不可比擬的優勢,這也是動態規划算法的核心之處。確定了動態規劃的這三要素,整個求解過程就可以用一個最優決策表來描述,最優決策表是一個二維表,其中行表示決策的階段,列表示問題狀態,表格需要填寫的資料一般對應此問題的在某個階段某個狀態下的最優值(如最短路徑,最長公用子序列,最大價值等),填表的過程就是根據遞推關係,從1行1列開始,以行或者列優先的順序,依次填寫表格,最後根據整個表格的資料通過簡單的取捨或者運算求得問題的最優解。

下面用一個實際例子來體現動態規劃的演算法思想——硬幣找零問題。

 

硬幣找零問題描述:現存在一堆面值為 V1、V2、V3…個單位的硬幣,問最少需要多少個硬幣才能找出總值為
T個單位的零錢?假設這一堆面值分別為 1、2、5、21、25 元,需要找出總值 T 為 63 元的零錢。

 

很明顯,只要拿出 3 個 21 元的硬幣就湊夠了 63 元了。

 

基於上述動態規劃的思想,我們可以從 1 元開始計算出最少需要幾個硬幣,然後再求 2 元、3元…每一次求得的結果都儲存在一個數組中,以後需要用到時則直接取出即可。那麼我們什麼時候需要這些子問題的解呢?如何體現出由子問題的解得到較大問題的解呢?

 

其實,在我們從 1 元開始依次找零時,可以嘗試一下當前要找零的面值(這裡指 1 元)是否能夠被分解成另一個已求解的面值的找零需要的硬幣個數再加上這一堆硬幣中的某個面值之和,如果這樣分解之後最終的硬幣數是最少的,那麼問題就得到答案了。

 

單是上面的文字描述太抽象,先假定以下變數:

 

values[] : 儲存每一種硬幣的幣值的數組

valueKinds :幣值不同的硬幣種類數量,即values[]數組的大小

money : 需要找零的面值

coinsUsed[] : 儲存面值為 i的紙幣找零所需的最小硬幣數

 

演算法描述:

 

當求解總面值為 i 的找零最少硬幣數 coinsUsed[ i ] 時,將其分解成求解 coinsUsed[ i – cents]和一個面值為 cents 元的硬幣,由於 i – cents < i , 其解 coinsUsed[ i – cents] 已經存在,如果面值為 cents 的硬幣滿足題意,那麼最終解 coinsUsed[ i ] 則等於 coinsUsed[ i – cents] 再加上 1(即面值為 cents)的這一個硬幣。


演算法實現:

package com.dynamic;/** * @author sjmei * @date 2012-10-30 */public class CoinsChange {/** *  * 硬幣找零:動態規划算法 * @param values:儲存每一種硬幣的幣值的數組 *  * @param valueKinds:幣值不同的硬幣種類數量,即coinValue[]數組的大小 *  * @param money:需要找零的面值 *  * @param coinsUsed:儲存面值為i的紙幣找零所需的最小硬幣數 */public static void makeChange(int[] values, int valueKinds, int money,int[] coinsUsed,int[] coinTrack) {coinsUsed[0] = 0;int last = 0;// 對每一分錢都找零,即儲存子問題的解以備用,即填表for (int cents = 1; cents <= money; cents++) {// 當用最小幣值的硬幣找零時,所需硬幣數量最多int minCoins = 999;// 遍曆每一種面值的硬幣,看是否可作為找零的其中之一for (int kind = 0; kind < valueKinds; kind++) {// 若當前面值的硬幣小於當前的cents則分解問題並查表if (values[kind] <= cents) {int temp = coinsUsed[cents - values[kind]] + 1;if (temp < minCoins) {minCoins = temp;last = kind;}}}// 儲存最小硬幣數coinsUsed[cents] = minCoins;coinTrack[cents] = values[last];System.out.print("面值為 :" + (cents) + "的最小硬幣數 : "+coinsUsed[cents]);System.out.print(" 硬幣為:");trackPrint(cents, coinTrack);System.out.println();}}private static void trackPrint(int m,int[] coinTrack){if(m==0){return;}else {System.out.print(coinTrack[m]+" ");trackPrint(m-coinTrack[m], coinTrack);}}public static void main(String[] args) {// 硬幣面值預先已經按降序排列int[] coinValue = new int[] { 25, 21, 10, 5, 1 };// 需要找零的面值int money = 65;// 儲存每一個面值找零所需的最小硬幣數,0號單元捨棄不用,所以要多加1int[] coinsUsed = new int[money+1];int[] coinTrack = new int[money+1];for(int i=1;i<=money;i++){coinsUsed[i] = 0;coinTrack[i] = 0;}makeChange(coinValue, coinValue.length, money, coinsUsed,coinTrack);}}

程式運行結果:

面值為 :1的最小硬幣數 : 1 硬幣為:1 面值為 :2的最小硬幣數 : 2 硬幣為:1 1 面值為 :3的最小硬幣數 : 3 硬幣為:1 1 1 面值為 :4的最小硬幣數 : 4 硬幣為:1 1 1 1 面值為 :5的最小硬幣數 : 1 硬幣為:5 面值為 :6的最小硬幣數 : 2 硬幣為:5 1 面值為 :7的最小硬幣數 : 3 硬幣為:5 1 1 面值為 :8的最小硬幣數 : 4 硬幣為:5 1 1 1 面值為 :9的最小硬幣數 : 5 硬幣為:5 1 1 1 1 面值為 :10的最小硬幣數 : 1 硬幣為:10 面值為 :11的最小硬幣數 : 2 硬幣為:10 1 面值為 :12的最小硬幣數 : 3 硬幣為:10 1 1 面值為 :13的最小硬幣數 : 4 硬幣為:10 1 1 1 面值為 :14的最小硬幣數 : 5 硬幣為:10 1 1 1 1 面值為 :15的最小硬幣數 : 2 硬幣為:10 5 面值為 :16的最小硬幣數 : 3 硬幣為:10 5 1 面值為 :17的最小硬幣數 : 4 硬幣為:10 5 1 1 面值為 :18的最小硬幣數 : 5 硬幣為:10 5 1 1 1 面值為 :19的最小硬幣數 : 6 硬幣為:10 5 1 1 1 1 面值為 :20的最小硬幣數 : 2 硬幣為:10 10 面值為 :21的最小硬幣數 : 1 硬幣為:21 面值為 :22的最小硬幣數 : 2 硬幣為:21 1 面值為 :23的最小硬幣數 : 3 硬幣為:21 1 1 面值為 :24的最小硬幣數 : 4 硬幣為:21 1 1 1 面值為 :25的最小硬幣數 : 1 硬幣為:25 面值為 :26的最小硬幣數 : 2 硬幣為:25 1 面值為 :27的最小硬幣數 : 3 硬幣為:25 1 1 面值為 :28的最小硬幣數 : 4 硬幣為:25 1 1 1 面值為 :29的最小硬幣數 : 5 硬幣為:25 1 1 1 1 面值為 :30的最小硬幣數 : 2 硬幣為:25 5 面值為 :31的最小硬幣數 : 2 硬幣為:21 10 面值為 :32的最小硬幣數 : 3 硬幣為:21 10 1 面值為 :33的最小硬幣數 : 4 硬幣為:21 10 1 1 面值為 :34的最小硬幣數 : 5 硬幣為:21 10 1 1 1 面值為 :35的最小硬幣數 : 2 硬幣為:25 10 面值為 :36的最小硬幣數 : 3 硬幣為:25 10 1 面值為 :37的最小硬幣數 : 4 硬幣為:25 10 1 1 面值為 :38的最小硬幣數 : 5 硬幣為:25 10 1 1 1 面值為 :39的最小硬幣數 : 6 硬幣為:25 10 1 1 1 1 面值為 :40的最小硬幣數 : 3 硬幣為:25 10 5 面值為 :41的最小硬幣數 : 3 硬幣為:21 10 10 面值為 :42的最小硬幣數 : 2 硬幣為:21 21 面值為 :43的最小硬幣數 : 3 硬幣為:21 21 1 面值為 :44的最小硬幣數 : 4 硬幣為:21 21 1 1 面值為 :45的最小硬幣數 : 3 硬幣為:25 10 10 面值為 :46的最小硬幣數 : 2 硬幣為:25 21 面值為 :47的最小硬幣數 : 3 硬幣為:25 21 1 面值為 :48的最小硬幣數 : 4 硬幣為:25 21 1 1 面值為 :49的最小硬幣數 : 5 硬幣為:25 21 1 1 1 面值為 :50的最小硬幣數 : 2 硬幣為:25 25 面值為 :51的最小硬幣數 : 3 硬幣為:25 25 1 面值為 :52的最小硬幣數 : 3 硬幣為:21 21 10 面值為 :53的最小硬幣數 : 4 硬幣為:21 21 10 1 面值為 :54的最小硬幣數 : 5 硬幣為:21 21 10 1 1 面值為 :55的最小硬幣數 : 3 硬幣為:25 25 5 面值為 :56的最小硬幣數 : 3 硬幣為:25 21 10 面值為 :57的最小硬幣數 : 4 硬幣為:25 21 10 1 面值為 :58的最小硬幣數 : 5 硬幣為:25 21 10 1 1 面值為 :59的最小硬幣數 : 6 硬幣為:25 21 10 1 1 1 面值為 :60的最小硬幣數 : 3 硬幣為:25 25 10 面值為 :61的最小硬幣數 : 4 硬幣為:25 25 10 1 面值為 :62的最小硬幣數 : 4 硬幣為:21 21 10 10 面值為 :63的最小硬幣數 : 3 硬幣為:21 21 21 面值為 :64的最小硬幣數 : 4 硬幣為:21 21 21 1 面值為 :65的最小硬幣數 : 4 硬幣為:25 25 10 5 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.