今天早上看一位搞cv的高手坯子的博客,看到了一个找零问题,觉得蛮有意思的,现分享如下:
问题描述:
现存在一堆面值为 V1、V2、V3 … 个单位的硬币,问最少需要多少个硬币才能找出总值为 T 个单位的零钱?假设这一堆面值分别为 1、2、5、21、25 元,需要找出总值 T 为 63 元的零钱。
很明显,只要拿出 3 个 21 元的硬币就凑够了 63 元了。
问题分析:
找63元,如果直接找最大面值,则有25+25+5+5+2+1,明显不是最小硬币数,所以直接找最大面值不行。怎样才能找到21呢?
将问题分解,分别找到63-1,63-2,63-5,63-21,63-25的最小硬币数,这五个中最小的,一定也就是组成63的最小硬币数。如果求62需要求61,60,57,41,37, 求61需要求60,59,56,40,36...
用conisUsed表示每一个面值对应的最小硬币数,则: coinsUsed[i]=coinsUsed[i-coinValue]+1;
用coinsTrack数组记录每一个面值对应的coinValue;
可以考虑使用动态规划方法:
动态规划的基本思想是将待求解问题分解成若干个子问题,先求解子问题,并将这些子问题的解保存起来,如果以后在求解较大子问题的时候需要用到这些子问题的解,就可以直接取出这些已经计算过的解而免去重复运算。保存子问题的解可以使用填表方式,例如保存在数组中。
具体代码如下:
#include <cstdlib>
#include <iostream>
using namespace std;
/*
@param coinValue:
保存硬币面值的数组
@param coinSize
保存面值的种类,即coniValue的大小
@param moneyValve
需要找零的面值
@param coinsUsed
记录每一个面值所对应的最小硬币数
@param coinsTrack
记录组成每一个面值所对应的,使其硬币数最小所加入的硬币面值
*/
void MakeChange(int* coinValue,size_t coinSize,int moneyValue,int *coinsUsed,int *coinsTrack)
{
//给coinsUsed、coinsTrack赋初值
coinsUsed[0]=0;
coinsTrack[0]=0;
//对moneyValue以下的所有money面值都要计算,都要求解以备用 ,cent为当前面值
for(int cent=1;cent<=moneyValue;cent++)
{
//记录每种money面值所对应的子问题的最小值,minCons初值为全部为1的硬币数
int minCoins=cent;
//记录每种money面值所对应的加入的硬币面值
int lastIn=0;
//分别对每种coin面值,求解其子问题
for(int j=0;j<coinSize;j++)
{
//如果当前面值大于等于硬币面值,则继续;否则退出
if(cent>=coinValue[j])
{
//找到最小的子问题解
if(coinsUsed[cent-coinValue[j]]<minCoins)
{
minCoins=coinsUsed[cent-coinValue[j]];
lastIn=coinValue[j];
}
}
}
//保存最小硬币数
coinsUsed[cent]=minCoins+1;
coinsTrack[cent]=lastIn;
}
//输出结果
cout<<"面值为"<<moneyValue<<"所需要的最小硬币为:"<<coinsUsed[moneyValue];
cout<<" 其面值分别为:";
while(moneyValue>0)
{
cout<<coinsTrack[moneyValue]<<" ";
moneyValue-=coinsTrack[moneyValue];
}
}
int main(int argc, char *argv[])
{
//硬币面值
int value[]={1,2,5,21,25};
//硬币数组大小
size_t size=sizeof(value)/sizeof(*value);
//要找零的数
int money=45;
//保存每个面值对应最小值,因为0号位置舍弃,故加1
int* coinsUsed=new int[money+1];
int* coinsTrack=new int[money+1];
MakeChange(value,size,money,coinsUsed,coinsTrack);
system("PAUSE");
return EXIT_SUCCESS;
}