动态规划应用--找零钱

文章目录

    • 1. 问题描述
    • 2. 问题分析
      • 2.1 回溯法求解
      • 2.2 DP状态转移方程法
      • 2.3 DP状态转移表法

1. 问题描述

找零问题,在贪心算法讲过。但是贪心不一定能得出最优解。假设有几种不同币值的硬币v1,v2,.……vn(单位是元)。如果要支付w元,求最少需要多少个硬币。比如,有3种不同的硬币,1元、3元、5元,我们要支付9元,最少需要3个硬币(3个3元的硬币)。

2. 问题分析

2.1 回溯法求解

/**
 * @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;
}

在这里插入图片描述

2.2 DP状态转移方程法

由于上面的钞票面额可能不止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;
    //如何打印出选取钞票的面额和张数???
}

在这里插入图片描述

2.3 DP状态转移表法

/**
 * @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;
}

在这里插入图片描述
在这里插入图片描述

你可能感兴趣的:(算法,《数据结构与算法之美》学习笔记)