动态规划:找钱问题

时间:2025-02-09 07:06:56

问题描述:

有一个特别的国度,只发行4种面值的硬币,分别是1硬币5硬币11硬币50硬币小明去售货机前买饮料,饮料售价35元一瓶,小明投入了50元硬币。现在售货机要15给他。假设每种硬币的数量充足,现在要求使用最少数量的硬币,给小明找钱,求出这个最少数量是多少。

 

问题分析:

售货机要给小明找回15元零钱,而现在只有4种面值的硬币可以使用,现在的核心问题是如何使用这4种面值的硬币来凑齐15块钱,且使用的硬币数目最少。

一、要凑齐15元。

二、使用的硬币数目最少。

 

贪心算法:

每一次尽可能拿出最大的面值钞票找剩下的钱,钱数减去当前钞票的面值,如此循环直到剩下的钱数为0。此算法有例外的情况,假设如今只有3种面值的钞票,分别是1,7,10,要求找出14元钱。贪心算法得到的答案是{10,1,1,1,1},需要5张钞票,而实际情况下最佳找钱方案是{7,7},只需要两张钞票。贪心算法保证了每一步取得的是局部最优解,不能保证最终结果是最优解。

 

动态规划算法:

解决方案每次只在已知的条件增加一个一种硬币,使其数额达到找钱数,在这几种方案中选择一个使用硬币数量最小的一种方案

f(i) = n :

i找钱数,取值范围:(0<=i<=15)

n凑出i所需要的硬币数量n个。

f使硬币数量n最小最佳函数

现在从0元钱开始找:

0元钱:因为0元根本不需要硬币,所以结果是  f(0) = 0,作为初始化已知条件。

1元钱:因为只有1元的硬币可以使用,所以可以先用11元硬币,然后再凑够0元即可,由于f(0)是我们已经算出来的,并且使用其他硬币均无法达成约束条件,所以

  f(1) = 1 + f(0) = 1 + 0 = 1

注意等式f(1) = 1 + f(0)右边的1代表11元的硬币,硬币数量。

2元钱:因为只有1元的硬币可以使用,所以先用11元硬币凑出1元钱,然后再凑够1元即可,由于f(1)是我们已经算出来的,并且使用其他硬币均无法达成约束条件,所以

  f(2) = 1 + f(1) = 1 + 1 = 2

注意这里f(2) = f(1) + 1,这里加上的1代表11元硬币。

3元钱:重复以上步骤  f(3) = 1 + f(2) = 3

4元钱:重复以上步骤  f(4) = 1 + f(3) = 4

5元钱:这里就有不一样的选择了,因为有5元硬币可以使用。

方案一:先用11元硬币,然后再凑够4元钱,即  1 + f(4) = 5,注意这里的1代表11元的硬币;

方案二:先用15元硬币,然后再凑够0元钱,即  1 + f(0) = 1,注意这里的1代表15元的硬币。

综合两种方案,有  f(5) = min{ 1 + f(4)1 + f(0) } = 1

6元钱:

方案一:先用11元硬币,然后再凑够5元钱,1 + f(5) = 2;

方案二:先用15元硬币,然后再凑够1元钱,1 + f(1) = 2;

综合两种方案,有  f(6) = min{ 1 + f(5)1 + f(1) } = 2

……省略

15元钱:

方案一:先用11元硬币,然后再凑够14元钱,1 + f(14) = 5;

方案二:先用15元硬币,然后再凑够10元钱,1 + f(10) = 2;

方案三:先用111元硬币,然后再凑够4元钱,1 + f(4) = 5;

综合两种方案,有  f(6) = min{ 1 + f(14)1 + f(10), 1 + f(4)} = 2

 

跟据上面的分析,要凑够i元,就要考虑如下各种方案的最小值:

  f(i) = min{ 1 + f( i – value[j] ) }i > 10 <= j <= num;

  value[]存储了各种面值,value[j]表示第j(0<=j<num)种面值,与其中1表示的面值相同。

  num表示有多少种面值。

  f(0) = 0为已知条件,因此 i > 1

#include <>
const int num = 3; //钱币的面值数
int value[num] = {1,7,11}; //钱币的面值

void change(int n) { //找钱方法
	int money[n+1] = {0}, min, note, sum; 
	int record[n+1] = {0}, num_value[num] = {0}; //record[i]记录i块钱最后用哪张面值的钞票找出,num_value根据record计算出找钱方案
	for (int i = 1; i <= n; i++) {
		min = n;
		for (int j = 0; j < num; j++) { 
			if (i >= value[j] && min > money[i-value[j]]) {
				min = money[i-value[j]]; //在前面几个找钱方案中找出最小的值 
				note = j; //记录用了哪张钞票 
			}
		}
		money[i] = min+1; 
		record[i] = value[note];
	}
	printf("最少钱币张数: %d\n", money[n]);
	printf("找钱方案:\n");
	printf("面值	张数\n");
	sum = n;
	while (sum > 0) {//有n元钱时,最后用的钞票面值为record[n] 
		for (int i = 0; i < num; i++) 
			if (record[sum] == value[i])
				num_value[i]++;
		sum -= record[sum];
	}
	for (int i = 0; i < num; i++) {
		printf("%d	%d\n", value[i], num_value[i]);
	}
}


int main() {
	int n; //要找的钱数
	scanf("%d", &n);
	change(n);
	return 0;
}