首頁>Club>
11
回覆列表
  • 1 # 淺析架構

    概念

    遞迴如果層次太多,就會照成棧溢位異常,因為每呼叫一次就會新生成一個棧幀,使用這個棧幀保留當前函式的狀態值。如果沒有必要儲存狀態值,那麼就可以複用棧幀,不會造成棧溢位。

    舉例

    這裡以求n的階乘舉例:

    正常遞迴:

    假如n=3;那麼每一步都需要保留n值以及下一步函式的返回值,所以每次呼叫都需要建立一個新的棧幀

    尾遞迴:

    假如n=3,這裡每一次呼叫是可以複用棧幀的,因為不需要儲存狀態值。

    總結

    所以說當遞迴是在當前棧幀執行完之後,不需要再保留當前棧幀,而是帶著當前棧幀的結果,進入到下一棧幀,就可以最佳化為尾遞迴,一般尾遞迴需要滿足遞迴呼叫是函式體中最後執行的語句。比如階乘的例子中最後執行的語句是直接呼叫factorial(n-1, n*result),而不是一個表示式n * factorial(n -1),如果是表示式的話就需要一個棧幀來保留n和factorial(n -1)的結果。

  • 2 # 思考思考的動物

    相對於遞迴,尾遞迴當然是好的,因為尾遞迴在呼叫時,直接覆蓋當前呼叫棧,棧不增長(這稱為 尾遞迴最佳化),但對於迴圈來說就是不見得了。

    理論上 尾遞迴和迴圈等價。所以,尾遞迴能實現的需求,迴圈一定可以實現,反之亦然。例如,用歐幾里得輾轉相除法,求取最大公因數(JavaScript),

    尾遞迴:

    var gcd = (a, b) => b ? gcd(b, a % b) : a;

    迴圈:

    var gcd = (a, b) => {

    }

    不過,一般來說,尾遞迴的實現程式碼更精妙,也更為直觀。

    對於有些函式式語言,尾遞迴是必不可少不的,例如:Lisp的迴圈語句都是用尾遞迴實現的(scheme)

    (define-syntax while

    而有些語言,僅僅將尾遞迴提供出來作為特殊情況下使用的工具,本身並不依賴,例如:JavaScript。很多的語言本身根本不支援尾遞迴最佳化,例如:C。

    一般來說,支援 λ表示式的語言,大多支援尾遞迴最佳化。

    通常情況下,大多數語言在寫正式程式碼中都不建議使用遞迴,不過在寫特殊程式時是允許的,例如:編譯器(裡面的程式處處是直接或間接遞迴)。另外,如果是在學習中 或 可行性分析階段,寫一些練習、原型程式碼,遞迴是不錯的選擇。

    對於某些函式式語言,尾遞迴是被鼓勵的,例如:Haskell,

    gcd r 0 = r

    gcd a b = gcd b (a `mod` b)

    這時,要儘量將普通遞迴,化為尾遞迴,絕大多數情況都可以,例如(JavaScript):

    階乘:

    非尾遞迴:var fact = n => n > 1 ? n * fact(n-1) : 1; 尾遞迴:var fact = (n, total = 1) => n > 1 ? fact(n-1, n * total) : total;

    斐波那契數列:

    非尾遞迴:var fib = n => n > 2 ? fib(n-1) + fib(n-2) : 1;

    尾遞迴:var fib = (n, a = 1, b = 1) => n > 2 ? fib(n-1, b, a + b) : b;

    最後,不管是否使用遞迴(尾遞迴),我們都需要熟練掌握它,作為一種思維方法而存在。

  • 3 # P指向為NULL

    貼一個自己寫的針對python語言來實現自定義尾遞迴最佳化。也就是把原來的尾遞迴函式改造成一個生成器,然後再在另一個函數里去迴圈next方法。

  • 中秋節和大豐收的關聯?
  • 秋季有什麼好吃的大鍋菜?