Recursion 球拍中的结构递归到累加递归

Recursion 球拍中的结构递归到累加递归,recursion,scheme,racket,Recursion,Scheme,Racket,我有一些代码来查找最大高度,并用关联的名称替换它。高度和名称有单独的列表,每个列表长度相同且不为空 我可以使用结构递归来解决这个问题,但必须将其转换为累加,我不确定如何做到这一点。我看到的所有例子都让我困惑。有人能用累加递归将代码转换成一个吗 (define (tallest names heights) (cond [(empty? names) heights] [(> (first heights) (first (rest heights))) (co

我有一些代码来查找最大高度,并用关联的名称替换它。高度和名称有单独的列表,每个列表长度相同且不为空

我可以使用结构递归来解决这个问题,但必须将其转换为累加,我不确定如何做到这一点。我看到的所有例子都让我困惑。有人能用累加递归将代码转换成一个吗

(define (tallest names heights)
  (cond
    [(empty? names) heights]
    [(> (first heights) (first (rest heights)))
     (cons (first names) (tallest (rest (rest names)) (rest (rest heights))))]
    [else (tallest (rest names) (rest heights))]))

首先,您提供的
highest
函数实际上不起作用(调用
(highest'(Bernie Raj Amy)(列表1.5 1.6 1.7))
失败,出现合同错误),但我明白您的意思。结构递归和累积递归有什么区别

(define (tallest names heights)
  (cond
    [(empty? names) heights]
    [(> (first heights) (first (rest heights)))
     (cons (first names) (tallest (rest (rest names)) (rest (rest heights))))]
    [else (tallest (rest names) (rest heights))]))

结构递归的工作原理是将结构构建为返回值,其中该结构中的一个值是对同一函数的递归调用的结果。以阶乘的递归计算为例。您可以这样定义它:

(define (factorial n)
  (if (zero? n) 1
      (* n (factorial (sub1 n)))))
(define (factorial n acc)
  (if (zero? n) acc
      (factorial (sub1 n) (* acc n))))
可视化该程序如何执行输入,例如,
4
。每次调用都会在乘法表达式中留下一个“洞”,由递归子调用的结果填充。下面是用
\uu
表示其中一个“洞”的可视化效果

请注意,大部分工作是如何在最终案例完成后才完成的。大部分工作必须在调用返回时弹出堆栈的过程中完成,因为每个调用都取决于对其子调用的结果执行一些额外的操作


累积递归有何不同?在累加递归中,我们对函数使用了一个额外的参数,称为累加器。重写上述阶乘函数以使用累加器将使其如下所示:

(define (factorial n)
  (if (zero? n) 1
      (* n (factorial (sub1 n)))))
(define (factorial n acc)
  (if (zero? n) acc
      (factorial (sub1 n) (* acc n))))
现在,如果我们想找到4的阶乘,我们必须调用
(阶乘41)
,为累加器提供一个起始值(稍后我将讨论如何避免)。如果您现在考虑调用堆栈,它看起来会完全不同

请注意,
factorial
函数的结果是累加器还是对自身的直接调用,没有需要填充的“漏洞”。这被称为尾部调用,对
factorial
的递归调用被称为处于尾部位置

事实证明,出于几个原因,这是有帮助的。首先,有些函数更容易用累加递归来表示,尽管阶乘可能不是其中之一。然而,更重要的是,该方案要求实现提供适当的尾部调用(有时也称为“尾部调用优化”),这意味着在进行尾部调用时,调用堆栈不会深入增长

关于尾部调用如何工作以及它们为什么有用,现有的信息很多,所以我在这里不再重复。重要的是要理解累加递归涉及累加器参数,这通常会导致结果函数通过尾部调用实现

但是对于额外的参数我们该怎么办呢?实际上,我们可以创建一个“helper”函数来进行累加递归,但我们将提供一个自动填充基本情况的函数

(define (factorial n)
  (define (factorial-helper n acc)
    (if (zero? n) acc
        (factorial-helper (sub1 n) (* acc n))))
  (factorial-helper n 1))
这种习惯用法非常常见,Racket提供了一种“named
let
”形式,将上述函数简化为:

(define (factorial n)
  (let helper ([n n] [acc 1])
    (if (zero? n) acc
        (helper (sub1 n) (* acc n)))))
但这只是对同一个想法的一些句法上的甜点


好的,那么:这些如何适用于你的问题?实际上,使用累积递归可以使问题的实现变得非常简单。下面是如何构建算法的分解图:

  • 就像在最初的示例中一样,您将遍历列表,直到
    为空。这将构成你的“基本情况”
  • 你的累加器将很简单,它将是你找到的当前最大元素
  • 在每次迭代中,如果您发现一个元素大于当前最大值,则该元素将成为新的累加器。否则,蓄能器保持不变
  • 把这些放在一起,下面是一个简单的实现:

    (define (tallest-helper names heights current-tallest)
      (cond
        [(empty? names)
         (car current-tallest)]
        [(> (first heights) (cdr current-tallest))
         (tallest-helper (rest names) (rest heights)
                         (cons (first names) (first heights)))]
        [else
         (tallest-helper (rest names) (rest heights)
                         current-tallest)]))
    
    这可以通过多种方式进一步改进,将其封装在一个函数中,该函数提供累加器的起始值,使用命名的
    let
    ,删除一些重复,等等-但我将把它留给您作为练习


    请记住:累加器实际上是您的“工作总和”。这是你的“跑步总数”。理解这一点,事情就会有意义。

    我不太清楚这段代码应该做什么。你能给出一些输入和输出的示例吗?(检查expect(最高的(列出Bernie Raj Amy)(列出1.5 1.7 1.6))'Raj)