找零问题,在贪心算法讲过。但是贪心不一定能得出最优解。假设有几种不同币值的硬币v1,v2,.……vn(单位是元)。如果要支付w元,求最少需要多少个硬币。比如,有3种不同的硬币,1元、3元、5元,我们要支付9元,最少需要3个硬币(3个3元的硬币)。
/**
* @description: 找零钱,需要张数最少,回溯法
* @author: michael ming
* @date: 2019/7/20 22:50
* @modified by:
*/
#include
#define N 3
int rmb[N] = {1,9,10};//钞票面额
int amount[N];
int minAmount[N];
using namespace std;
void exchange(const int &targetMoney, int curMoney, int &minPiece, int piece)
{
if(curMoney > targetMoney)//超过目标,返回
return;
if(curMoney == targetMoney)//达到目标金额
{
if(piece < minPiece)
{
minPiece = piece;//更新最小张数
for(int i = 0; i < N; ++i)
minAmount[i] = amount[i];//获取每张钞票的张数
}
return;
}
for(int i = 0; i < N; ++i)
{//递归调用,拿取每张面额的钞票
amount[i]++;
exchange(targetMoney,curMoney+rmb[i],minPiece,piece+1);
amount[i]--;//恢复上次的状态
}
}
int main()
{
int minPiece = 65535, piece = 0,targetMoney = 18, curMoney = 0;
exchange(targetMoney,curMoney,minPiece,piece);
cout << "凑成" << targetMoney << "元,最少需要:" << minPiece << "张(枚)。" << endl;
int i = 0;
while(i < N)
{
if(minAmount[i] != 0)
cout << minAmount[i] << "个" << rmb[i] << " ";
i++;
}
cout << endl;
cout << "----------------------" << endl;
}
由于上面的钞票面额可能不止3种,递归树是多叉树,所以状态转移表法画起回溯的递归图比较麻烦,我们采用状态转移方程法。
状态转移方程如下:
minPiece(targetMoney) = 1 + min{minPiece(targetMoney-rmb[0]), ... , minPiece(targetMoney-rmb[N-1])}
targetMoney = 18;//目标金额
rmb[N] = {1,9,10};//钞票面额
对于题目的情况,代入具体数值,状态转移方程如下
minPiece(18) = 1 + min{minPiece(18-1), minPiece(18-9) , minPiece(18-10)}
= 1 + min{minPiece(17),minPiece(9),minPiece(8)}
DP(递归+备忘录)代码如下:
/**
* @description: 找零钱,需要张数最少
* @author: michael ming
* @date: 2019/7/20 18:35
* @modified by:
*/
#include
#include
#include
#define N 3
const int targetMoney = 18;//目标金额
int rmb[N] = {1,9,10};//钞票面额
int mem[targetMoney+1];//备忘录,存放最小张数
using namespace std;
int minP(int Money)
{
if(Money < 0)//超过目标,返回很大的张数,表示不可能凑成
return 65535;
if(Money == 0)//达到目标金额
return 0;
if(mem[Money] > 0)//计算过了,直接读取备忘录
return mem[Money];
int minAmount[N];
memset(minAmount,65535,N*sizeof(int));
for(int i = 0; i < N; ++i)
{//递归调用,拿取每张面额的钞票
minAmount[i] = minP(Money-rmb[i]);
}
sort(minAmount,minAmount+N);
mem[Money] = minAmount[0]+1;//记录最小的张数
return mem[Money];
}
int main()
{
cout << "凑成" << targetMoney << "元,最少需要:"
<< minP(targetMoney) << "张(枚)。" << endl;
//如何打印出选取钞票的面额和张数???
}
/**
* @description: 找零钱,需要张数最少,dp状态表法
* @author: michael ming
* @date: 2019/7/21 20:01
* @modified by:
*/
#include
#include
#include
#define N 3
const int targetMoney = 18;//目标金额
int rmb[N] = {1,9,10};//钞票面额,从小到大
using namespace std;
void exchange(int Money)
{
int maxPiece = targetMoney/rmb[0];//最大张数
int i, j, k;
int (*states)[targetMoney+1] = new int [maxPiece][targetMoney+1];
//memset(states,65535,maxPiece*(targetMoney+1)*sizeof(int));
//上面错误!!!memset一般只付0或极大值
for(i = 0; i < maxPiece; ++i)
for(j = 0; j <= targetMoney; ++j)
states[i][j] = 65535;//初始化
for(k = 0, j = 0; j <= targetMoney; ++j)
{
if(k < N && j == rmb[k])
{//初始化第一行数据
states[0][j] = 1;//一张rmb
k++;
}
}
for(i = 1; i < maxPiece; ++i)//动态规划
{
for(j = 0; j <= targetMoney; ++j)//上面一行的数据考下来
states[i][j] = states[i-1][j];
for(j = 0; j <= targetMoney; ++j)
{
if(states[i-1][j] != 65535)
{
for(k = 0; k < N; ++k)
{
if(j+rmb[k] <= targetMoney && states[i-1][j+rmb[k]] > states[i-1][j]+1)
states[i][j+rmb[k]] = states[i-1][j]+1;
}
}
}
}
cout << "凑成" << targetMoney << "元,最少需要:"
<< states[maxPiece-1][targetMoney] << "张(枚)。" << endl;
//------------打印选择的信息---------------------------
for(i = maxPiece-1; i >= 1 && states[i][targetMoney] == states[i-1][targetMoney]; --i)
;//此时i等于最早出现的答案处的行
for(j = targetMoney; j > 0; )
{
if(i != 0)
{
for(k = 0; k < N; ++k)
{
if(states[i-1][j-rmb[k]] == states[i][j]-1)
{
cout << "1张" << rmb[k] << " ";
j = j-rmb[k];
i--;
break;
}
}
}
else
{
cout << "1张" << j << " ";
break;
}
}
delete [] states;//释放资源
}
int main()
{
exchange(targetMoney);
return 0;
}