莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          用遞歸計算階乘咋不行呢?

          Posted on 2008-03-18 19:34 dennis 閱讀(7700) 評論(11)  編輯  收藏 所屬分類: 計算機科學與基礎
              讀《代碼大全2》,已經讀了一半,喘口氣??偨Y八個字:百科全書,受益匪淺。小到一個賦值語句、一個循環的編寫,大到需求分析、架構設計,無所不包,看后半部分目錄,更是扯到了重構、軟件工藝、程序員的性格特征這樣的話題。恰好手邊的工作暫時比較有閑,可以實踐下“創建高質量的代碼”中的部分建議,晚上讀書,第二天就重構,樂在其中。這一部分中對設計、子程序、類、變量、語句的處理建議,可能你平常已經在這么做,可作者這么精辟地概括出來讓人嘆服,而有些地方是你平常絕對很少注意的,特別是在變量和三種常見控制語句的處理上。
            
              說說我認為是缺點的地方,就是作者貌似對函數式語言了解很少,舉的例子全部用的是廣泛應用的靜態語言(c/c++,java,vb)。例如作者有這么一句話:如果為我工作的程序員用遞歸去計算階乘,那么我寧愿換人。作者對遞歸的態度相當謹慎,這在靜態命令式語言中顯然是正確的,但是在函數式語言中,由于有尾遞歸優化的存在,遞歸反而是最自然的形式,況且我打心里認為遞歸更符合人類思維。請注意,在FP中只有尾遞歸的程序才是線性迭代的,否則寫出來的遞歸可能是線性遞歸或者樹形遞歸,兩種情況下都可能導致堆棧溢出并且性能較差。

          scheme寫階乘:
          (define (fac n)
            (
          if (= 1 n)
                
          1
                (
          * n (fac (- n 1)))))
          顯然這個版本不是尾遞歸,計算過程是一個線性遞歸過程,計算(fac 4)的過程如下:
          (* 4 (fac 3))
          (* 4  (3 * (fac 2)))
          (* 4  (3 * (* 2 (fac 1))))
          (* 4  (3 * (* 2 1)))
          (* 4  (3 * 2))
          (* 4 6)
          24
              因為解釋器是采用應用序求值,需要將表達式完全展開,然后依次求值,在這個過程中,解釋器內部需要保存一條長長的推遲計算的軌跡。
          改寫成一個尾遞歸版本:
          (define (fac n)
            (define (fac
          -iter product n)
              (
          if (= 1 n)
                  product
                  (fac
          -iter (* n product) (- n 1))))
            (fac
          -iter 1 n))
          我們來看看它的計算過程:
          (fac-iter 1 4)
          (fac-iter 4 3)
          (fac-iter 12 2)
          (fac-iter 24 1)
          24
          可以看到,在這個過程中,解釋器不需要保存計算軌跡,迭代的中間結果通過product變量來保存,這是一個線性迭代的計算過程。
          最后再看一個斐波拉契數列的例子:
          (define (fib n)
            (cond ((
          = n 0) 0)
                      ((
          = n 11)
                      (
          else
                           (+ (fib (- n 1))  (fib (- n 2))))))

          這個計算過程展開是一個樹形遞歸的過程(為什么說是樹形?展開下計算過程就知道),改寫為線性迭代:
          (define (fib n)
            (define (fib
          -iter a b count)
               (
          if (= count 0)
                   b
                   (fib
          -iter (+ a b) a (- count 1))))
           (fib
          -iter 1 0 n))

               上述的內容在sicp第一章里有更詳細的介紹和討論。


          評論

          # re: 用遞歸計算階乘咋不行呢?  回復  更多評論   

          2008-03-18 19:44 by 久城
          不了解函數式語言,但是《代碼大全》還是很不錯的。

          剛進公司時買了一本...

          很遺憾,到現在也沒看完幾章...

          有時間要好好學習一下了。

          # re: 用遞歸計算階乘咋不行呢?  回復  更多評論   

          2008-03-19 17:20 by CowNew開源團隊
          階乘的非遞歸算法也很簡單呀,不知道為什么很多教科書講階乘的時候都用遞歸算法。
          //實例性代碼,未考慮大數值的計算溢出問題
          private int calcFact(int value)
          {
          int fact = 1;
          for (int i = 2; i <= value; i++)
          {
          fact *= i;
          }
          return fact;
          }

          # re: 用遞歸計算階乘咋不行呢?  回復  更多評論   

          2008-03-19 17:36 by ZelluX
          @CowNew開源團隊
          Thinking in Lisp...

          # re: 用遞歸計算階乘咋不行呢?  回復  更多評論   

          2008-03-19 17:47 by dennis
          @CowNew開源團隊
          教科書上講階乘都是用遞歸算法,這是因為階乘就是用遞歸定義的嘛
          n=1 fac(n)=1
          n>1 fac(n)=n*fac(n-1)

          類似Lisp這樣的函數式語言能將數學公式直接描述出來(what to do,而非how to do),Lisp本來就是為了解決符號推斷而發展起來的,比如用Lisp求函數導數,你能想象用java做出來嗎?做出來也夠嗆。

          # re: 用遞歸計算階乘咋不行呢?  回復  更多評論   

          2008-03-19 19:27 by CowNew開源團隊
          @dennis
          階乘就是用遞歸定義的嗎?“N的階乘等于1*2*3*4*……*(N-1)*N”

          # re: 用遞歸計算階乘咋不行呢?  回復  更多評論   

          2008-03-19 20:34 by ZelluX
          @dennis
          不是吧。。遞歸定義只是一種表達形式而已。。
          Lisp只是多了將函數作為基本類型的特性而已,描述上和其他語言還是一樣的吧,SICP序言中就提到了它們都是imperative的,而不是數學中更常見的declarative。
          Java不能求導不就是因為Java沒函數指針或者說callback機制不夠強大嗎

          # re: 用遞歸計算階乘咋不行呢?  回復  更多評論   

          2008-03-20 09:15 by dennis
          @ZelluX
          嗯,我得承認我的表述很不恰當,我的本意是Scheme更容易將說明性的數學公式直接翻譯為表達式。Lisp模擬函數求導很容易,除了將 函數作為一等公民的特性外,更重要的是可以將符號作為數據來平等對待,如果用java來做,恐怕要自己搞一個類似詞法分析的東東。

          # re: 用遞歸計算階乘咋不行呢?  回復  更多評論   

          2008-03-20 09:40 by ZelluX
          @dennis
          需要什么詞法分析。。
          dy/dx不就行了嗎

          double derivative = (f(x + EPISILON) - f(x)) / EPESILON;

          # re: 用遞歸計算階乘咋不行呢?  回復  更多評論   

          2008-03-20 10:07 by dennis
          @ZelluX
          我說的是符號求導,比如求函數2x^2的導數是4x

          # re: 用遞歸計算階乘咋不行呢?[未登錄]  回復  更多評論   

          2008-04-08 19:54 by 閑耘
          才知道尾遞歸,學習。
          http://blog.xianyun.org/2008/04/08/factorial-and-tail-recursion/

          # re: 用遞歸計算階乘咋不行呢?  回復  更多評論   

          2010-02-01 18:03 by zagfai
          @CowNew開源團隊

          無知...

          函數式沒循環
          主站蜘蛛池模板: 沂源县| 保德县| 广德县| 黎平县| 平罗县| 兴宁市| 龙口市| 苏尼特右旗| 华宁县| 云安县| 双峰县| 巴林左旗| 朔州市| 白沙| 刚察县| 四川省| 永丰县| 库伦旗| 迁西县| 永州市| 峡江县| 息烽县| 泉州市| 洱源县| 贡觉县| 深水埗区| 鄯善县| 额尔古纳市| 农安县| 昆山市| 平邑县| 司法| 香河县| 长岭县| 菏泽市| 阜宁县| 慈溪市| 旬阳县| 华坪县| 元江| 文昌市|