SICP-零錢置換的正確迭代實現

遞歸和迭代的轉化,關鍵需要明確哪些是遞歸的冗余數據,也就說哪些是迭代可以重復利用數據。下面具體分析。

給不同的coin分配索引

(define (first-denomination kinds-of-coins)
    (cond ((= kinds-of-coins 1) 1)
        ((= kinds-of-coins 2) 5)
        ((= kinds-of-coins 3) 10)
        ((= kinds-of-coins 4) 25)
        ((= kinds-of-coins 5) 50)))

遞歸的思路

將總數為a的現金換成n種硬幣的不同方式的數目等于

  1. 將現金a換成除第一種硬幣以外的其他硬幣的不同方式,加上2
  2. 將現金a-d換成所有種類硬幣的不同方式。其中d為第一種硬幣的面值。

可以寫遞歸公式

Ct(N) = Ct(N-first-denomination(t)) + Ct-1(N)

t(下標)為幾種硬幣,N為現金數。例如:t為5,N為100美分,所以總數目=(將100美分換成1,5,10,25這四種硬幣組成方法數)+(將100-50美分換成1,5,10,25,50這五種硬幣組成的方法數)

通過公式進行初步運算,漸漸會發(fā)現冗余數據(重復利用的數據)

C5(100)=C4(100)+C5(50)
    C4(100)==C3(100)+C4(75)
        C3(100)=C2(100)+C3(90)
            C2(100)=C1(100)+C2(95)
                C2(95)=C1(95)+C2(90)
                    C2(90)=C1(90)+C2(85)
                        C2(85)=C1(85)+C2(80)
                            C2(80)=C1(80)+C2(75)
                                C2(75)=C1(75)+C2(70)
                                    ...
            # C2(90) 重復
            C3(90)=C2(90)+C3(80)
                # C2(80) 重復
                C3(80)=C2(80)+C3(70)
                    # C2(70) 重復
                    C3(70)=C2(70)+C3(60)
                        ...
        C4(75)=C3(75)+C4(50)
            C3(75)=C2(75)+C3(65)
                ...
            C4(50)=C3(50)+C4(25)
                ...
    C5(50)=C4(50)+C5(0)
        C5(50)=C4(50)+C5(0)
            ...

上面可能不太直觀

C2(4) = C1(4) + C2(-1)
C2(5) = C1(5) + C2(0)
C2(6) = C1(6) + C2(1)
C2(7) = C1(7) + C2(2)
C2(8) = C1(8) + C2(3)
C2(9) = C1(9) + C2(4) //出現重復利用值C2(4) 間隔為5
C2(10) = C1(10) + C2(5) //出現重復利用值C2(5) 間隔為5
C2(11) = C1(11) + C2(6)
C2(12) = C1(12) + C2(7)
C2(13) = C1(13) + C2(8)
C2(14) = C1(14) + C2(9)
C2(15) = C1(15) + C2(10)
C2(16) = C1(16) + C2(11)


C3(4) = C2(4) + C3(-6)
C3(5) = C2(5) + C3(-5)
C3(6) = C2(6) + C3(-4)
C3(7) = C2(7) + C3(-3)
C3(8) = C2(8) + C3(-2)
C3(9) = C2(9) + C3(-1)
C3(10) = C2(10) + C3(0)
C3(11) = C2(11) + C3(1)
C3(12) = C2(12) + C3(2)
C3(13) = C2(13) + C3(3)
C3(14) = C2(14) + C3(4) //出現重復利用值C3(4) 間隔為10
C3(15) = C2(15) + C3(5) //出現重復利用值C3(5) 間隔為10
C3(16) = C2(16) + C3(6)

所以對于C2來說,始終需要緩存5個可以重復利用值(長度為5的緩存隊列);對于C3,始終需要緩存10個可以重復利用值(長度為10的緩存隊列);對于C4,使用需要緩存25個(...);對于C5來說,使用需要緩存50個可以重復利用值(...)

迭代思路

  1. 迭代是線性O(n)時間+常量空間消耗(不會隨n改變)
  2. 迭代需要重復利用遞歸產生的冗余數據.
  3. 迭代的狀態(tài)能由這些變量完全刻畫

假設有5種硬幣,現金100美分

C 源碼

/*
 * =====================================================================================
 *
 *  Filename:  p26.c
 *
 *  Description: change money
 *
 *  Version:  1.0
 *  Created:  2016/08/02 14時58分22秒
 *  Revision:  none
 *  Compiler:  gcc
 *
 *  Author:  jmpews (jmpews.github.io), jmpews@gmail.com
 *
 * =====================================================================================
 */


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int count_change(int amount);
int cc(int amount, int kinds_of_coins);
void count_iter(int *tmp, int t, int amount);
int get_coin(int index_of_coin);
int get_index_tmp(int index_of_coin);
int *get_tmp_array(int kinds_of_coins);
int get_recycle_value(int index_of_coin, int current_amount, int *tmp_array);
void update_recycle_value(int index_of_coin, int *tmp_array, int value);

int main ( int argc, char *argv[] )
{
    int t;
    t = count_change(100);
    printf("%d", t);
    return EXIT_SUCCESS;
}               /* ----------  end of function main  ---------- */

int count_change(int amount) {
    cc(amount, 5);
    return 0;
}

int cc(int amount, int kinds_of_coins) {
    int *tmp = get_tmp_array(kinds_of_coins);
    int t = 0;
    tmp[0] = 0;
    count_iter(tmp, t, amount);
    return 0;
}

// 這里這里也是關鍵點,這個尾遞歸的結束由t(當前需要兌換的金錢)和amount(需要兌換的目標金錢)控制,為線性,也就是說時間復雜度為O(n)
void count_iter(int *tmp, int t, int amount) {
    int r;
    r = get_recycle_value(1, t, tmp);
    update_recycle_value(1, tmp, r);
    
    //C2(t) = C2(t-get_coin(2)) + C1(t)
    r = get_recycle_value(2, t, tmp) + r;
    update_recycle_value(2, tmp, r);
    
    //C3(t) = C3(t-get_coin(3)) + C2(t)
    r = get_recycle_value(3, t, tmp) + r;
    update_recycle_value(3, tmp, r);
    
    //C4(t) = C4(t-get_coin(4)) + C3(t)
    r= get_recycle_value(4, t, tmp) + r;
    update_recycle_value(4, tmp, r);
    
    //C5(t) = C5(t-get_coin(5)) + C4(t)
    r = get_recycle_value(5, t, tmp) + r;
    if(t == amount) {
        printf("final-value: %d\n", r);
        exit(1);
    }
    update_recycle_value(5, tmp, r);

    count_iter(tmp, t+1, amount);
}

int get_coin(int index_of_coin) {
    switch(index_of_coin) {
        case 1: return 1;
        case 2: return 5;
        case 3: return 10;
        case 4: return 25;
        case 5: return 50;
        default: exit(1);
    }
}

// 對于C1、C2、C3、C4、C5緩存隊列開始的位置
int get_index_tmp(int index_of_coin) {
    switch(index_of_coin) {
        case 1: return 0;
        case 2: return 1;
        case 3: return 6;
        case 4: return 16;
        case 5: return 41;
        default: exit(1);
    }
}

// 分配固定的緩存, 無論需要兌換多少金錢,只要金幣種類不變,緩存的大小就是固定的。 空間復雜度為常量。
// "因為它的狀態(tài)能由其中的三個狀態(tài)變量完全刻畫,解釋器在執(zhí)行 這一計算過程時,只需要保存這三個變量的軌跡就足夠了" 這句話在這里就有體現了
int *get_tmp_array(int kinds_of_coins) {
    int *tmp;
    int i;
    int sum = 0;
    for(i=1 ; i<kinds_of_coins ; i++) {
        sum += get_coin(i);
    }
    tmp = (int *)malloc(sizeof(int) * sum);
    memset(tmp, 0 ,sizeof(int) * sum);
    return tmp;
}

// 獲取重復利用值, 每次緩存隊列頭的位置
// 比如: 此時緩存隊列為[C2(0), C2(1), C2(2), C2(3), C2(4)]
// C2(5) = C1(5) + C2(0) 此時我們需要取緩存隊列頭的值C2(0)
// 計算完得到C2(5),需要執(zhí)行update_recycle_value將得到C2(5)進隊列,除去舊的C2(0),此時隊列頭尾C2(1),即為計算C2(6)需要的緩存值
int get_recycle_value(int index_of_coin, int current_amount, int *tmp_array) {
    int t = get_index_tmp(index_of_coin);
    if(current_amount < get_coin(index_of_coin)){
        return 0;
    }
    else if(current_amount == get_coin(index_of_coin)){
        return 1;
    }
    else {
        return tmp_array[t];
    }
}

// 更新重復利用值(隊列的概念), 計算出最新的值,需要替換舊的利用值
// 比如: C2(5) = C1(5) + C2(0)
// 現在C2緩存隊列中有[C2(0), C2(1), C2(2), C2(3), C2(4)],我們需要將C2(5)進隊列,[C2(1), C2(2), C2(3), C2(4), C2(5)]
void update_recycle_value(int index_of_coin, int *tmp_array, int value) {
    int i;
    int t = get_index_tmp(index_of_coin);
    for(i = 0; i< (get_coin(index_of_coin)-1); i++) {
        tmp_array[t+i] = tmp_array[t+i+1];
    }
    tmp_array[t+get_coin(index_of_coin)-1] = value;
}

參考

http://stackoverflow.com/questions/1485022/sicp-making-change/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,733評論 18 399
  • 一. Java基礎部分.................................................
    wy_sure閱讀 3,829評論 0 11
  • 3.2 函數和所生成的過程 來源:3.2 Functions and the Processes They G...
    布客飛龍閱讀 936評論 0 37
  • 流星劃過一剎那的故事 在那一刻虔誠的許下我的世界 從心里面留下未來的愿景 煙火燦爛一時的美好 在那一刻匆忙的身影駐...
    劃一座城池閱讀 139評論 0 2
  • 交通燈一直很喜歡自己的工作。他在繁忙的十字路口高高矗立著,有三只大大的眼睛,左右一紅一綠,中間一黃。他的工作...
    Clearness閱讀 364評論 0 1