Ackermann函数的尾递归实现

曾经在微博中谈到在可计算理论中著名的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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值