• 换零钱问题的非递归解法 SICP 1.2.2中的一个问题


    经典问题:换零钱方式的统计

    问题介绍

    现在有若干不同面额的零钱,供顾客来换。零钱种类有 0.5美元,0.25美元,10美分,5美分和1美分五种(这里也可以自定义,程序改动的地方也很简单)。
    计算当顾客用a元换零钱时,共有多少种兑换方法?

    解法描述(这里照搬sicp中的内容)

    将总数为a的现金换成n种硬币的不同方式的数目等于:
    - 将现金数a换成除第一种硬币之外的所有其它硬币的不同方式的数目,加上
    - 将现金数a-d换成所有种类的硬币的不同方式数目,其中d是第一种硬币的面额

    我们根据上面的算法定义,就可以得到如下算法:
    - 如果a=0,属于兑换成功,因此属于1中兑换方式
    - 如果a<0,兑换失败,属于0种兑换方式
    - 如果n=0,兑换失败,属于0中兑换方式

    sicp中的递归方法

    (define (count-change amount) (cc amount 5))
    (define (cc amount kinds-of-coins)
      (cond ((= amount 0) 1)
            ((or (< amount 0) (= kinds-of-coins 0)) 0)
            (else (+ (cc amount
                         (- kinds-of-coins 1))
                     (cc (- amount
                            (first-denomination
                             kinds-of-coins))
                         kinds-of-coins)))))
    (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)))
    
    (count-change 100)
    ;;292
    

    我们知道,递归有一个缺点,如果不能做到尾递归消除,那么,调用栈很快会爆炸,因此,上面的解法只能计算比较小的值,如果有面额10000的,
    可能就没办法了。
    递归和循环是可以替换的,只是有些递归方法转换成循环非常麻烦,甚至仅仅是理论上的可转换(如ackerman函数,可能都做不到循环,这我不知道啊!我只是打个比方)
    但是循环有一个巨大的优势,它不会消耗栈空间,因此,如果能将上面的方法改写为循环的方式(或者是尾递归),那么就可以计算很大的值了。

    clojure的解法

    因为jvm不支持尾递归,因此,clojure提供了recur函数,可以将尾递归转换为循环形式。下面就是clojure的解法

    ;; money change
    
    ;; $1/2 $1/4 $1/10 $1/20 $1/100
    ;;半美元,1/4美元,10美分,5美分,1美分 换零钱
    ;;多少种换法
    
     
    (def money-kinds [50 25 10 5 1])
    
    (defn finish?
      ;;判断该参数列表是否已经计算完毕
      ;;完成条件:
      ;;1,可兑换的硬币种类只剩下一种(这里通过判断元素个数是否为2),即1美分的(注意,
      ;;如果最小的硬币面额不是1美分的,还要判断
      ;;当前的余额是否能够整除,如果不能整除,则属于不能兑换的情况),返回0
      ;;2,如果当前余额为0,说明已经兑换完了,返回0
      ;;3,如果当前余额为负数,说明兑换失败了,返回0,表示本次兑换无效,不能计入总数
      ;;4,如果不满足以上情况,说明还没有兑换结束,直接返回该参数列表
      ;;例:[25,10,5,1,50] -> [25,10,5,1,50]
      ;;   [1,25] -> 1
      ;;   [10,5,1,0] -> 1
      ;;   [10,5,1,-5] -> 0
      [coins&money]
      (cond
        (= 2 (count coins&money)) 1
        (= 0 (last coins&money)) 1
        (> 0 (last coins&money)) 0
        :default coins&money))
    
    (defn change-helper
      ;;处理当前的参数列表,也就是换零钱的递归定义
      ;;例: [100,50,25,10,5,1,100] -> [[50,25,10,5,1,100] [100,50,25,10,5,1,100-100]]
      [coins&money]
      [(subvec coins&money 1 (count coins&money))
       (conj (pop coins&money) (- (peek coins&money) (first coins&money)))])
    
    
    (def t [[10,5,1,25] [1,10] [10,5,1,0] [10,5,1,-1]])
    
    (defn compute
      ;;计算当前参数列表序列的结果
      ;;[[10 5 1 25] [1 10] [10 5 1 0] [10 5 1 -1]] -> ([10 5 1 25] 1 1 0)
      [holder]
      (map finish? holder))
    
    (defn get-cur-r
      ;;从当前的计算结果中取得所有兑换结束的结果
      ;;([10 5 1 25] 1 1 0) -> 2
      [compr]
      (apply + 
             (filter #(not (coll? %)) compr)))
    
    (defn get-cur-col
      ;;保留当前计算结果中未兑换完的参数列表
      ;;([10 5 1 25] 1 1 0) -> ([10 5 1 25])
      [combs]
      (filter coll? combs))
    
    
    (defn change
      ;;主要的兑换过程
      ;; coins array of coin kinds [50 25 10 5 1]
      ;; money money to change n
      [coins money]
      (let [cm (conj coins money)] ;;[50 25 10 5 1 100]
        (loop [holder [cm]     ;;[[50 25 10 5 1 100]]
               result 0]     ;;0
          (if (empty? holder)
            result
            (let [[f & rest] holder ;;f: [25,10,5,1,100] rest: [[50,25,10,5,1,50]]
                  h (change-helper f);; [[10,5,1,100] [25 10 5 1 75]]
                  h1 (compute h) ;; ([10,5,1,100] [25 10 5 1 75])
                  c (get-cur-col h1);; [[10,5,1,100] [25 10 5 1 75]]
                  r (get-cur-r h1)];; 0
              (recur (into rest c) (+ result r)))))))
    
    
    (def coins [50 25 10 5 1])
    (def money 100)
    
    (def cm [(conj coins money)])
    ;;(prn cm)
    
    (def h (change-helper (first cm)))
    ;;(prn h)
    (def h1 (compute h))
    ;;(prn h1)
    
    (def c (get-cur-col h1))
    ;;(prn c)
    (def r (get-cur-r h1))
    ;;(prn r)
    
    (change coins money)
    ;;292
    

    我没有验证该方法的正确性,只验证了100元的兑换方案为292种,500元有59576种,800元有343145种,1000元有801451种,如果你也实现了,还请帮我验证一下
    1000元的,如果使用递归方法,可能就不行了
    谢谢

  • 相关阅读:
    关于Request.QueryString参数的重构
    简单剖析Asp.Net MVC 源码中的三个IoC注入点
    如何更优雅的实现ExtJS的前端MVC模式下一个App多个Controller的动态加载
    Cenos 7.X重置密码
    openwrt 旁路由设置
    必看的20个小众精品网站
    一句话PYTHON FTP
    ubuntu/kylin 安装FTP
    支付宝转账
    appium环境变量设置
  • 原文地址:https://www.cnblogs.com/xiaojintao/p/6358035.html
Copyright © 2020-2023  润新知