Ackermann函数的尾递归实现

时间:2021-08-04 02:30:57

曾经在微博中谈到在可计算理论中著名的Ackermann函数。当时接触到的算法是Rózsa Péter 在1935年提出的算法:

A(0, n):= n + 1 for n ≥ 0 A(m, 0):= A(m - 1, 1) for m > 0 A(m, n):= A(m - 1, A(m, n - 1))  for m, n > 0

该算法基于两个参变量的递归函数,但不是尾递归的。由于此算法对递归深度的增加是超过了指数级的,因此在32位的系统中,这个算法很快就能将堆栈消耗光。首先给出此算法的CL代码:

 

;;Ackermann Function, 1935 Rozsa Peter, classic mad benchmark
(defun ack (m n)
  (cond ((= m 0) (1+ n))
            ((= n 0) (ack (1- m) 1))
            (t (ack (1- m) (ack m (1- n))))))

 

对于较大的m值,此函数递归深度增加的速度非常快,因此实际上计算较大的m和n的结果将是不可能的。

(虽然他是一个可计算函数,但是却非primitive recursive(不能用有限的迭代来代替)的)

 

今天在lambda-the-ultimate.org中看到一篇关于尾递归的讨论贴,其中有关于Ackermann函数的尾递归实现(scheme代码),其基本思想是用树结构来记录在Rozsa Peter算法中(特别是第三个表达式)需要的中间结果,将对堆栈的依赖转移到对树的创建与遍历。以下是这个尾递归Ackermann函数的CL代码:

 

;;Ackermann Function Tail Recursive, in lambda-the-ultimate.org
(defun ack-tr (m n)
  (cond ((null m) n)
            (t (let ((nm 0) (nn 0))
             (cond ((numberp m)       ;;对用户传入参数的处理。若m不是树,则将其变成树根节
                        (progn
                          (setf nm (list m))
                          (setf nn n)))
                       ((= (first m) 0)      ;;对应A(0, n):= n + 1 for n ≥ 0,以求出值,删除节点
                        (progn
                          (setf nm (rest m))
                          (setf nn (1+ n))))
                       ((= n 0)                ;;对应A(m, 0):= A(m - 1, 1) for m > 0,替换节点,继续求值
                        (progn
                          (setf nm (cons (1- (first m)) (rest m)))
                          (setf nn 1)))
                       (t   ;;对应A(m, n):= A(m - 1, A(m, n - 1))  for m, n > 0,增加节点,代替内层函数求值
                        (progn
                          (setf nm (cons (first m)
                                         (cons (1- (first m))
                                               (rest m))))
                          (setf nn (1- n)))))
                 (ack-tr nm nn)))))       ;;尾递归在这里出现!

 

由于尾递归可以很容易的被编译器优化为迭代,因此,上述的尾递归算法实际对堆栈的消耗是个常量。不过,为了维护列表m,实际的开销并没有减少。

 

Reference:

http://home.versatel.nl/vspickelen/Largefiles/Ackerman.htm

http://lambda-the-ultimate.org/node/2673

http://en.wikipedia.org/wiki/Ackermann_function