延续传递风格使事物尾递归?

时间:2011-07-25 14:23:56

标签: recursion scheme tail-recursion towers-of-hanoi

在这里问这个很痛苦。确实如此。每次我徒劳地寻找我的烦恼的答案,我都会看到它。嘲弄我。 Stack Overflow

无论如何,一些地狱般的影响使我试图解决河内之塔。我的第一个解决方案是不完整的,因为如果运行太多磁盘会导致memory error

(define hanoi
  (lambda (n from to other)
    (cond ((< n 0)
       (error `(Error! Number of disks ,n cannot be less than 0!)))
      ((= n 0)
       '())
      (else
       (append (hanoi (- n 1)
              from
              other
              to)
           `((,from ,to))
           (hanoi (- n 1)
              other
              to
              from))))))

我在某处读到延续传递风格可以解决问题。但是,这didn't help either

(define hanoi_cps
  (lambda (n from to other c)
    (cond ((< n 0)
       (error `(Error! Number of disks ,n cannot be less than 0!)))
      ((= n 0)
       (c '()))
      (else
       (hanoi_cps (- n 1)
              from
              other
              to
              (lambda (x)
            ((lambda (w)
               (w `((,from ,to))))
             (lambda (y)
               (hanoi_cps (- n 1)
                      other
                      to
                      from
                      (lambda (z)
                    (c (append x y z))))))))))))

2 个答案:

答案 0 :(得分:14)

在继续传递样式中,而不是使用递归调用扩展堆栈空间,而是在执行连续执行的环境中构建递归定义的lambda ...换句话说,内存在某处使用这条线。例如,使用简单的因子算法,您通常会将其写为:

(define (factorial x)
    (cond ((eq? x 0) 1))
          ((eq? x 1) 1))
          (else (* x (factorial (- x 1))))))

使用factorial的递归定义,堆栈空间将用于保存每个递归函数调用中执行的延迟乘法运算的参数。同一函数的延续传递版本如下所示:

(define (factorial x cont)
    (cond ((eq? x 0) (cont 1))
          ((eq? x 1) (cont 1))
          (else (factorial (- x 1) (lambda (y) (cont (* x y)))))))

以前消耗的堆栈空间现在被匿名lambda的环境耗尽了。在这种情况下,lambda的环境充满了每次递归调用x时解析contfactorial的值所需的值。由于cont本身是一个带有环境的lambda,你可以看到内存最终将如何被消耗,因为每个lambda-continuation需要在其环境中存储从之前调用factorial的lambda ...这会创建一个递归的定义的lambda-continuation,其环境基本上是通过对factorial的递归调用累积的所有延续的递归列表。

查看延续传递样式的一种方法是,虽然你基本上将函数调用机制转换为尾递归方法,但是continuation本身的实际定义本质上是递归的,所以你不是真的删除算法本身的递归性质...换句话说,评估通过尾递归调用构建的延续需要在其内部评估递归定义的延续,它本身在其内部具有另一个递归定义的延续,等等。 lambda-continuations的环境最终看起来像列表列表等等。在lambda-continuation的环境中存储所有这些递归定义需要内存,所以无论你是否正在消耗通过正常的递归调用约定在堆栈上的空间,或者通过在每个lambda-continuation中存储递归定义的环境来消耗内存空间,无论哪种方式,你最终都会耗尽空间。

答案 1 :(得分:3)

CPS不会帮助您提高内存效率,因为通过执行它,您只需用匿名函数替换堆栈帧。如果您希望程序使用更少的内存,请尝试回溯搜索(但请注意,您必须小心避免无限移动序列)。