【问题标题】:How to achieve tail recursion on a tree-shaped data structure如何在树形数据结构上实现尾递归
【发布时间】:2015-02-10 07:07:12
【问题描述】:

我正在使用 Racket,但该问题适用于支持尾递归的任何方案。

我熟悉在平面列表上实现尾递归的传统模式,大致是这样的:

(define (func x [acc null])
  (if (null? x)
      acc
      (func (cdr x) (cons (do-something-to (car x)) acc))))

在这种情况下,func 位于尾部位置。

但是当我使用树时,即具有递归嵌套列表的列表时,我最终会使用 map 进行递归下降,如下所示:

(define (func2 x)
  (cond
    [(atom? x) (do-something-to x)]
    [(list? x) (map func2 x)]))

这可行,但func2 不再处于尾部位置。

您能否——如果可以,您将如何——以尾递归的方式重写func2

(抛开它是否提高性能的问题,这不是我要问的问题。)

【问题讨论】:

标签: algorithm recursion scheme racket tail-recursion


【解决方案1】:

从技术上讲,您“可以”通过引入一个充当堆栈的累加器。您的函数只会在堆栈为空时执行。

但是,这与使用函数调用堆栈(即非尾递归)具有相同的内存使用要求,因此通常这样做不会获得任何收益。

【讨论】:

  • 除了你的累加器可以是“无限的”而你的堆栈不能。
  • 这取决于实现。例如,如果您通过使用 CPS 转换而不是堆栈复制来实现延续,那么您的调用堆栈实际上将驻留在堆中,并且不受 CPU 堆栈的限制。
  • @ChristopheDeTroyer:即使没有 Chris 提到的 CPS hijinks,实际上也没有理由假设堆大于堆栈。至少在理论上,您可以使用链接器开关为堆栈保留 2^wordsize 字节的 VM,并且操作系统将根据需要将其分页。实际限制可能略低,并且取决于操作系统。
【解决方案2】:

正如在另一个答案中已经正确说明和解释的那样,对此使用尾递归没有任何好处。但是,由于您对它的完成方式感兴趣,这是我曾经实现过的deep-map 函数。如果您对镜像列表感到满意,代码会更简洁。

(define deep-map
  (λ (f lst)
    (let tail-rec ([stack `(,lst)] [acc '(())])
      ;(displayln (~a "Stack: " stack " / Acc: " acc))
      (cond [(null? (car stack))
             (if (null? (cdr stack))
                 (car acc)
                 (tail-rec (cdr stack)
                           `(,(append (cadr acc) `(,(car acc))) . ,(cddr acc))))]
            ;; The first element is a list and is being put on the stack
            [(list? (caar stack))
             (tail-rec `(,(caar stack) . (,(cdr (car stack)) . ,(cdr stack)))
                       `(() . ,acc))]
            ;; Process next element
            [else (tail-rec `(,(cdar stack) . ,(cdr stack)) 
                            `(,(append (car acc) `(,(f (caar stack)))) . ,(cdr acc)))]
            ))))

一个简单的例子:

> (deep-map add1 '(1 ((2) 3)))
Stack: ((1 ((2) 3))) / Acc: (())
Stack: ((((2) 3))) / Acc: ((2))
Stack: (((2) 3) ()) / Acc: (() (2))
Stack: ((2) (3) ()) / Acc: (() () (2))
Stack: (() (3) ()) / Acc: ((3) () (2))
Stack: ((3) ()) / Acc: (((3)) (2))
Stack: (() ()) / Acc: (((3) 4) (2))
Stack: (()) / Acc: ((2 ((3) 4)))
'(2 ((3) 4))

【讨论】:

  • 关于性能,DrRacket 中的一些快速测试表明deep-map 比简单的递归下降循环慢了一个数量级。
猜你喜欢
  • 2019-04-14
  • 1970-01-01
  • 2015-04-26
  • 2010-12-04
  • 2013-03-12
  • 1970-01-01
  • 2016-03-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多