Recursion 解决;“n-rooks”;带尾部递归

Recursion 解决;“n-rooks”;带尾部递归,recursion,lisp,common-lisp,tail-recursion,gnu-common-lisp,Recursion,Lisp,Common Lisp,Tail Recursion,Gnu Common Lisp,我试图用尾部递归来解决n rooks问题,因为它比标准递归快,但是我很难弄清楚如何使它全部工作。我查阅了这个问题背后的理论,发现解决方案是由一个叫做“电话号码”的东西给出的,它由以下等式给出: 其中T(1)=1,T(2)=2 我已经创建了一个递归函数来计算这个方程,但是它只在T(40)之前快速工作,我需要它来计算其中的n>1000,根据我的估计,目前需要几天的计算时间 尾部递归似乎是我最好的选择,但我希望这里的人可能知道如何使用尾部递归编程这个关系,因为我并不真正理解它 我使用的是LISP,但

我试图用尾部递归来解决n rooks问题,因为它比标准递归快,但是我很难弄清楚如何使它全部工作。我查阅了这个问题背后的理论,发现解决方案是由一个叫做“电话号码”的东西给出的,它由以下等式给出:

其中T(1)=1,T(2)=2

我已经创建了一个递归函数来计算这个方程,但是它只在T(40)之前快速工作,我需要它来计算其中的n>1000,根据我的估计,目前需要几天的计算时间

尾部递归似乎是我最好的选择,但我希望这里的人可能知道如何使用尾部递归编程这个关系,因为我并不真正理解它


我使用的是LISP,但我愿意使用任何支持尾部递归的语言。

尾部递归只是一个表示为递归的循环

当您有一个递归定义“forks”时,最简单的实现很可能是时间指数的,从T(n)到T(n-1)和T(n-2),然后是T(n-2)、T(n-3)、T(n-3)、T(n-4),每一步的计算量加倍

诀窍是反转计算,以便从T(1)和T(2)开始构建。这只需要在每一步都有固定的时间,所以整个计算是线性的

(let ((n 2)
      (t-n 2)
      (t-n-1 1))
  …)
让我们使用
do
循环进行更新:

(do ((n 2 (1+ n))
     (t-n 2 (+ t-n (* n t-n-1)))
     (t-n-1 1 t-n))
  …)
现在,您只需在达到所需的
n
时停止:

(defun telephone-number (x)
  (do ((n 2 (1+ n))
       (t-n 2 (+ t-n (* n t-n-1)))
       (t-n-1 1 t-n))
      ((= n x) t-n)))
要完整,请检查您的输入:

(defun telephone-number (x)
  (check-type x (integer 1))
  (if (< x 3)
      x
      (do ((n 2 (1+ n))
           (t-n 2 (+ t-n (* n t-n-1)))
           (t-n-1 1 t-n))
          ((= n x) t-n))))

当尾部递归被优化时,它会像循环一样缩放(但常数因子可能不同)。请注意,Common Lisp并不要求尾部调用优化。

这太棒了!但由于某些原因,输出数据似乎与预期不符。(即,(电话10)应为9496,但此代码为1890。)@user3745602,正确的版本如下:
(定义电话号码(x)(检查类型x(整数1))(如果(
@Renzo:您应该保持
n
跟踪当前
n
,因此从2开始,乘以
n
,将
n
x
进行比较。感谢您的更正,我将进行编辑(尽管尚未测试)。
(defun telephone (x)
  (labels ((tel-aux (n t-n t-n-1)
             (if (= n x)
                 t-n
                 (tel-aux (1+ n)
                          (+ t-n (* n t-n-1))
                          t-n))))
    (tel-aux 2 2 1)))