1. 程式人生 > >硬幣找零問題(動態規劃)

硬幣找零問題(動態規劃)

硬幣找零(動態規劃)

問題介紹

給定指定的硬幣種類,面值為 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();
}

總結

思想是動態規劃的思想,動態規劃有個動態方程,感覺這個問題動態方程比較簡單,寫出來可能還不如描述下思想,我感覺重點在於怎麼儲存找的方案,在此用的回溯法不斷的重寫固定下標的元素,這一點很重要。另外,該演算法使用遞迴,並且沒有儲存當前狀態,有優化的空間,可以用遞迴法。