最少錢幣數-1-貪心算法(錯,或者叫有問題)-CCF-CSP練習題(50)


目錄

題目:

分析:

貪心算法C++代碼(有問題):

總結:


來自湖大程序設計訓練系統(外網進不去,所以不貼鏈接了)。

題目:

最少錢幣數
問題描述
這是一個古老而又經典的問題。用給定的幾種錢幣湊成某個錢數,一般而言有多種方式。例如:給定了 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了),所以會錯過9+9這個最優解。所以說貪心算法在對問題求解時,總是做出在當前看來是最好的選擇,不從整體最優考慮。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM