【问题标题】:Tail recursion calling tail recursion尾递归调用尾递归
【发布时间】:2016-03-21 17:32:41
【问题描述】:

我正在尝试用尾递归解决帕斯卡三角形。我理解做尾递归,函数调用语句应该是最后一条指令。喜欢这里:

(defn pascal [line colum, acc]
  (if (or (= line 0) (= line colum) (= colum 0))
    (+ acc 1)
    (recur (dec line) colum
           (pascal (dec line) (dec colum), acc))))

我的问题是:由于我使用递归调用作为递归的参数,它仍然有效吗?

因为我无法替换这个:

(recur (dec line) colum
       (pascal (dec line) (dec colum), acc))))

到这里:

(recur (dec line) colum
       (recur (dec line) (dec colum), acc))))

最好的问候

【问题讨论】:

    标签: recursion clojure functional-programming tail-recursion


    【解决方案1】:

    也许我的回答与递归无关,而且这个问题与 @Sylwester 的回答完全不同,但展示解决此问题的另一种方法仍然很有用。

    假设帕斯卡三角形具有以下性质:

    1. 帕斯卡三角形每一行的第一项和最后一项都是'1'
    2. 第二个也是倒数第二个是行数
    3. 任何其他元素都可以通过公式解决:

    这意味着,您可以通过时间复杂度 O(n^3) 的线性算法求解帕斯卡三角形的任何元素。它可能并不比具有 O(n^2) 和递归的递归版本酷,但它不会破坏堆栈并且它使用组合,在我看来这更好,因为它更简单和更安全的版本。 所以,我们开始吧:

    (defn naive-factorial[n]
      (reduce *' (range 1 (inc n))))
    
    (defn combinatoric-formula[line pos]
      (if (<= pos line)
        (/ 
         (naive-factorial line) 
         (*' (naive-factorial pos) 
             (naive-factorial (- line pos))))))
    

    如您所见,使用了 naive-factorial 函数,该函数采用 n 乘法,导致我们得到 O(n^3)。它与您的函数相同,但没有任何递归。

    对于帕斯卡三角形,也有不止一种方法可以用不同的方式解决它们。有的很刁钻,有空的可以看看:rosettacode.org

    此外,在您使用 + 在 clojure 中使用 int 数学的版本中,请在任何可能导致大量数字的情况下使用 +' 函数(假设这意味着添加将导致转换您的值到 biginteger 类型,这允许非常大的数字)。

    我还将 @Sylwester 介绍的方案版本翻译成 clojure:

    (defn pascal [row col]
      (let [aux  
            (fn aux [tr tc prev acc]
              (cond (> tr row) (throw (.Exception "invalid input"))         
                    (and (= col tc) (= row tr)) (+' (first prev) (second prev)); the next number is the answer
                    (= tc tr) (recur (+' tr 1) 1 (cons 1 acc) '(1))                  ; new row 
                    :else (recur tr               ; new column
                               (+' tc 1) 
                               (next prev)
                               (cons (+' (first prev) (second prev)) acc))))]
        (if (or (zero? col) (= col row))
          1
          (aux 2 1 '(1 1) '(1)))))
    

    它也许可以改进,但显示了这个想法。它计算了从第三行到上一个提供的输入的整个三角形,然后得到答案。功能方法非常棒和纯粹的魔力。

    这个版本的性能比线性版本差很多。 所以它得到:

    (time (combinatoric-formula 1000 100))
    "Elapsed time: 2.73794 msecs" for linear version
    
    (time (pascal 1000 100))
    "Elapsed time: 135.426888 msecs" for tail recursion version
    

    但它仍然更清洁和凉爽;)

    【讨论】:

    • 太棒了!由于line-pos &lt;= pos &lt;= line 可以创建一个阶乘函数来获取先前的答案并继续计算。当你可以做到 O(row) 时,没有理由以我的方式浪费 cpu 循环。回到数学是:-)
    【解决方案2】:

    只有一半的调用是通过尾递归进行的,所以另一半可能会破坏堆栈。对比一下:

    (defn factorial (n) 
      (if (= n 1)
          1
          (* n (factorial (n - 1)))))
    

    这里(factorial (n - 1)) 需要在延续(* n &lt;result&gt;) 之前完成,这是一个堆栈帧,在递归运行时等待。

    总比没有尾随要好,但如果都是和it is possible!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-17
      • 1970-01-01
      • 2017-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-03
      • 1970-01-01
      相关资源
      最近更新 更多