最少钱币数-1-贪心算法(错在哪里)-编程练习题(50)

目录

题目:

分析:

贪心算法C++代码(有问题):

总结:


自己保存的编程练习题,可以看这里CG-OJ系统编程练习题

题目:

最少钱币数
问题描述
这是一个古老而又经典的问题。用给定的几种钱币凑成某个钱数,一般而言有多种方式。例如:给定了 6 种钱币面值为 2、5、10、20、50、100,用来凑 15 元,可以用 5 个 2 元、1个 5 元,或者 3 个 5 元,或者 1 个 5 元、1个 10 元,等等。显然,最少需要 2 个钱币才能凑成 15 元。
        你的任务就是,给定若干个互不相同的钱币面值,编程计算,最少需要多少个钱币才能凑成某个给出的钱数。
输入形式 输入可以有多个测试用例。每个测试用例的第一行是待凑的钱数值 M(1 <= M<= 2000,整数),接着的一行中,第一个整数 K(1 <= K <= 10)表示币种个数,随后是 K个互不相同的钱币面值 Ki(1 <= Ki <= 1000)。输入 M=0 时结束。
输出形式 每个测试用例输出一行,即凑成钱数值 M 最少需要的钱币个数。如果凑钱失败,输出“Impossible”。你可以假设,每种待凑钱币的数量是无限多的。
样例输入 15
6 2 5 10 20 50 100
1
1 2
0
样例输出 2
Impossible

分析:

要想钱币数目最少,肯定紧着最大面值的钱用,当前剩余的钱数小于最大面值的钱的话就用第二大面值,以此类推,直到待凑的钱数值为0,或者不为0(则输出Impossible)。基于这种想法,算法上叫贪心算法,我们还可以每次都算一次Money除以maxCoins最大面值的钱数值,得到用几张最大钱币,余数即为剩余钱币数,这样我们只用循环钱币种数k次就能得出答案。

minCoin += money/coins[i]; //钱币数
money = money%coins[i];    //剩余钱数

然而并没有那么简单,考虑这样一组数据,例如有 1元,7元,9元,10元四种钱币的情况下,要凑18元 ,贪心算法会给出答案需要3张(10元1张,7元1张,1元1张)但是我们可以明显看出需要 2张(9元两张)足矣,显然贪心算法算出来的是错误的。那么怎么办呢,只有用动态规划算法了。详见:最少钱币数(凑硬币)详解-2-动态规划算法(初窥)-CCF-CSP练习题。为什么贪心算法有问题还贴出代码?仅供自己以后回忆参考,加深印象。

贪心算法C++代码(有问题):

#include <iostream>
#include <algorithm>

using namespace std;

int main()
{
    bool cmp(int a,int b);//降序排序比较函数
    int coins[10];//硬币数目,由于题目给出不超过10种,所以我申请了10。
    int money; /*待凑钱的数值*/
    int kind;  /*钱币种类数目*/
    int minCoin =0 ;//最少钱币数
    int i;
    while(1)
    {
        minCoin = 0;//每次初始化为0
        cin >> money;     //读入待凑钱数
        if(0 == money) break; //如果为0,则退出。
        cin >> kind;          //读入拥有钱币种类数
        for(i=0; i<kind; i++)
        {
            cin >> coins[i];  //依次读入钱币种类
        }
        sort(coins,coins+kind,cmp);  //降序排序钱币种类
        for(i=0; i<kind; i++)
        {
            if(money-coins[i] >= 0)
            {
                minCoin += money/coins[i];
                money = money%coins[i];
            }
        }
        if( 0 == money )
        {
            cout << minCoin << endl;
        }else{
            cout << "Impossible" <<endl;
        }

    }
    return 0;
}

bool cmp(int a,int b) /* 降序排序比较函数,当a>b时,不交换;当a<b时交换*/
{
    return a > b;
}

总结:

这个解法在oj系统中有问题,只得了50分,剩下的每组数据有1个是错误的。所以在CCF-CSP考试时如果不会动态规划的话可以把贪心算法写上去,能骗点分数。

使用贪心算法解决凑硬币问题时,有些情况下是可以得出正解的,比如后一个钱币面值没有达到前一个钱币面值的2倍时。对于有的问题得出的解是错误的。比如有3种面值分别为3元,5元,7元的纸币,(1)那么至少用几张纸币能凑够10元?我的直觉告诉我先选面值最大的,7元一张,然后再选面值5元的时候发现超额了(7+5>10),因此我们选3元一张,最少用2张纸币就能凑够10元,这个都能想出来的方法可以叫贪心算法(每次都选当前看来最好的选择,不从整体最优考虑),(2)那么至少用几张纸币能凑够8元呢?如果还按照贪心算法来解的话会得到Impossible。因为先选一张7元,然后再选5元(7+5>8)不行,换选3元(7+3>8)还不行。但是仔细看会发现5元+3元不是8元吗,怎么会无解。所以贪心算法解这个问题是不行的。CSP考试时只能用来骗点分。

到这里我们发现用贪心算法会出现两个问题 1)本来有解用贪心法算出来却无解,例如上例(2)至少用几张纸币能凑够8元?;2)算出来的解不是最优解,例如有 1元,7元,9元,10元四种面值的纸币,要凑18元 ,贪心算法会给出答案需要3张(10元1张,7元1张,1元1张),但是我们可以明显看出 2张(两张9元)才是最优解。

那么问题出在了哪里?在这里用贪心算法是有条件的——后一个的权值(这里就是纸币面值)是前一个的2倍或以上才可以使用,这里10不到9的两倍。贪心算法不是对所有问题都能得到整体最优解。关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。在这里我们先选择了10元,在第二步的时候9元就选择不了了(10+9>18了),选择10元这个状态影响到选9元这个状态,所以会错过9+9这个最优解。所以说贪心算法在对问题求解时,总是做出在当前看来是最好的选择,不从整体最优考虑。

posted @ 2018-10-14 16:16  CatOnRoad  阅读(770)  评论(0编辑  收藏  举报