看来我已经找到了将任何递归过程转换为尾递归的通用方法:
- 使用额外的“结果”参数定义帮助程序子过程。
- 将应用于过程的返回值的参数应用于该参数。
- 调用此帮助程序过程即可开始。“结果”参数的初始值是递归过程的退出点的值,因此,最终的迭代过程将从递归过程开始收缩的地方开始。
例如,这是要转换的原始递归过程(SICP练习1.17):
(define (fast-multiply a b)
(define (double num)
(* num 2))
(define (half num)
(/ num 2))
(cond ((= b 0) 0)
((even? b) (double (fast-multiply a (half b))))
(else (+ (fast-multiply a (- b 1)) a))))
这是转换后的尾递归过程(SICP练习1.18):
(define (fast-multiply a b)
(define (double n)
(* n 2))
(define (half n)
(/ n 2))
(define (multi-iter a b product)
(cond ((= b 0) product)
((even? b) (multi-iter a (half b) (double product)))
(else (multi-iter a (- b 1) (+ product a)))))
(multi-iter a b 0))
有人可以证明还是反对?
1
首先考虑:这可能适用于所有单个递归函数,但是如果它适用于进行多个递归调用的函数,我会感到惊讶,因为这暗示着,例如,您可以在不需要堆栈的情况下实现quicksort 空间。(快速排序的现有有效实现通常在堆栈上进行1次递归调用,并将其他递归调用转换为可以(手动或自动)转换为循环的尾调用。)
—
j_random_hacker
再想一想:选择
—
j_random_hacker 2016年
b
2的幂表示初始设置product
为0并不完全正确。但是在b
奇数时将其更改为1无效。也许您需要2个不同的累加器参数?
您尚未真正定义非尾递归定义的转换,添加一些结果参数并将其用于累加非常模糊,并且几乎不能推广到更复杂的情况,例如,您有两次递归调用的树遍历。但是,存在一个更精确的“继续”概念,您可以在其中进行部分工作,然后让“继续”功能接管您的工作,并将当前完成的工作作为参数。这称为连续传递样式(cps),请参见en.wikipedia.org/wiki/Continuation-passing_style。
—
Ariel
这些幻灯片fsl.cs.illinois.edu/images/d/d5/CS422-Fall-2006-13.pdf包含cps转换的描述,您可以在其中进行一些任意表达式(可能带有非尾调用的函数定义)并将其转换为只包含尾部调用的等效表达式。
—
Ariel
@j_random_hacker是的,我可以看到我的“转换”过程实际上是错误的……
—
nalzok