【问题标题】:Continuation passing style makes things tail recursive?延续传递风格使事情尾递归?
【发布时间】:2011-07-25 14:23:56
【问题描述】:

在这里问它很痛苦。确实如此。每次我徒劳地寻找问题的答案时,我都会看到它。嘲讽我。 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))))))))))))

【问题讨论】:

  • 多少磁盘是磁盘太多,出于好奇?
  • @dyoo for hanoi:19 个磁盘;对于hanoi_cps:15 个磁盘

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


【解决方案1】:

在延续传递风格中,不是通过递归调用扩展堆栈空间,而是在执行延续的环境中构建递归定义的 lambdas ......换句话说,内存在某处被用完线。例如,对于一个简单的阶乘算法,您通常会这样写:

(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 的环境填充了所需的值,以便在每次递归调用factorial 时解析xcont 的值。由于cont 本身是一个带有环境的 lambda,因此您可以看到最终将如何消耗内存,因为每个 lambda-continuation 都需要在其环境中存储上一次调用阶乘的 lambda ...这将创建一个递归定义的 lambda -continuation 的环境基本上是通过对factorial 的递归调用而产生的所有延续的递归列表。

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

【讨论】:

  • 啊,所以没有办法解决对内存的需求。
  • 不,不幸的是,这是递归算法的缺点……请记住,真正的“尾递归”调用约定实际上不是真正的递归算法,因为它可以通过循环进行交换。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 2012-04-19
    • 1970-01-01
    • 2011-12-16
    • 2011-06-30
    • 2010-12-21
    • 1970-01-01
    • 2017-03-25
    • 2012-12-12
    • 1970-01-01
    相关资源
    最近更新 更多