硬幣找零問題(動態規劃)
阿新 • • 發佈:2018-12-17
硬幣找零(動態規劃)
問題介紹
給定指定的硬幣種類,面值為 1, 3, 5(在此具體化些),給定所找零的錢數 sum,給出最少的硬幣找零數,每個種類的硬幣無限使用。
問題分析
看到這問題,當時我想到用貪心演算法來求解,最後求解方案因為巧合對了,後來在網上看到動態規劃的題目,才知道貪心演算法得不到最優解,比如 給定 面值為 1, 3, 4,給定找零數為 6,用貪心法得出方案 [4,1,1],但顯然 [3,3] 方案即可。分析下問題,想一下若存在最少硬幣數 num 滿足當前給定的找零數 sum,則是不是一定存在最少硬幣數 num-1 滿足找零數 sum - (1, 3, 5 )?答案是存在,想一想就知道,當然邊界除外。這個類似於最短路徑的 dijkstra演算法。
實現方案
對於上面這個思路的實現方案,我用的是回溯思想,擼一個遞迴函式,不斷遞迴查詢給定硬幣數,給定金額的方案。在此設定一個標誌位 is_find ,若找到 賦值為 true 。在外面套一層迴圈使得 硬幣數 從 1 不斷增加,若找不到則 硬幣數 + 1 繼續找,若找到 則退出迴圈(通過判斷 is_find ), 找的過程中需要儲存資料,在此宣告一個數組變數 coin_kind 儲存方案,每次遞迴將下標為 num 的陣列元素設定為 當前選擇的硬幣值,這是回溯的思想,具體看程式碼。
程式碼
#include<stdio.h> #include<stdlib.h> int coin[3] = {1, 3, 5}; /* 硬幣種類 */ int coin_kind_num = 3; /* 硬幣種類數量 */ int sum = 7; /* 找零錢數 */ int min_coin_num; /* 硬幣最少數 */ int coin_kind[1000]; /* 存放最少數的方案 */ bool is_find = false; /* 是否找到硬幣 */ /* 函式:列印方案 列印存放找零方案的 陣列coin_kind[] */ bool output(){ for (int i = 0; i < 1000; i++) { if (coin_kind[i] == -1) break; printf(" %d ", coin_kind[i]); } printf("\n"); } /* 函式:初始化方案陣列 初始化存放找零方案的數字 ,使元素全部為 0,以便判斷輸出 */ void initCoinKind(){ for (int i = 0; i < 100; i++) coin_kind[i] = -1; } /* 函式:查詢基本函式 該函式用於給定 指定硬幣數 和 指定應找零的錢數,看是否能找開,用的回溯法查詢. 思想是 若存在對於給定應找零的錢數 sum,存在最少硬幣數 num,則對於 sum - coin[i] ( i = 0, ..., coin_kind_num - 1) 仍然存在最少硬幣數 num - 1, 類似於 最短路徑那個 dijkstra演算法 。 @param num 指定硬幣數 @param sum 指定應找零的錢數 */ bool findCoin(int num, int sum){ /* 函式邏輯 若 給定指定硬幣數為 0 , 即此時已到期待邊界,若此時硬幣數 等於 0 ,說明已經找開,找到方案,賦值 is_find = true ( 作為 連續查詢終止條件 )。 大於 0 ,說明未找開, 此時剩餘應找開金額大於0。 小於 0 , 說明此時剩餘金額小於 0,不滿足。 若 給定指定金額硬幣數 不為 0(及 大於 0),說明此時未搜尋完,執行 遍歷存放硬幣種類的陣列 coin[], 將當前遍歷物件 存放到 *指定下標* 的 找零方案陣列 coin_kind[] 中。遞迴執行 該函式,硬幣數 - 1,應找零數 - 遍歷物件硬幣值 */ if (num == 0) { if (sum == 0){ is_find = true; printf("找到最少硬幣數 %d: ", min_coin_num); output(); return true; } else if (sum > 0){ return false; } else{ return false; } } else { for (int i = 0; i < coin_kind_num; i++) { coin_kind[num - 1] = coin[i]; findCoin(num - 1, sum - coin[i]); } } } /* 函式:硬幣數逐漸 + 1 查詢 由於查詢方案基本函式只能給定指定硬幣數 num 和指定找零數 sum, 所以 應該使得指定硬幣數從小到大, 從 1 開始,每次查詢完畢判斷 is_find 的值, 若為 false 則 num + 1,繼續查詢 否則 退出迴圈查詢完畢。 */ bool startFind(){ int num = 1; while (!is_find){ min_coin_num = num; findCoin(num, sum); num++; } } int main(){ initCoinKind(); /* 初始化存放方案的陣列 */ startFind(); }
總結
思想是動態規劃的思想,動態規劃有個動態方程,感覺這個問題動態方程比較簡單,寫出來可能還不如描述下思想,我感覺重點在於怎麼儲存找的方案,在此用的回溯法不斷的重寫固定下標的元素,這一點很重要。另外,該演算法使用遞迴,並且沒有儲存當前狀態,有優化的空間,可以用遞迴法。