Maison > Questions et réponses > le corps du texte
之所以会有这种想法,基于两个理由:
递归本质上是在进行压栈操作,人脑不易理解,尽管很多人都在尝试,相比之下,循环就容易理解多了。
这有一篇文章深入地介绍了递归,http://blog.csdn.net/theknotyouknow/article/details/24435291
循环执行效率比递归高很多。
在知乎上搜了一下,感觉回答的并不是很好,还请各位不吝赐教。
怪我咯2017-04-17 11:26:40
能。大学计科有讲这个的。而且反过来也成立。
你想啊,递归调用就是把参数压栈。改成循环,我手动建个栈,每次循环把需要的数据压进去,循环完弹出来不就可以了么。
递归 vs 循环:
高洛峰2017-04-17 11:26:40
当然能。
所谓递归无非就是函数(直接或间接)调用自己,所以我们先看一下简单的函数调用。
假设有一个函数长这样:
int foo() {
int X = 4
int Y = bar(14);
return X*Y;
}
你可以把它变成这样:
int foo() {
foo_stack_frame = gcalloc { X: int; Y: int }
foo_stack_frame->X = 4;
foo_stack_frame->Y = bar(14);
return foo_stack_frame->X * foo_stack_frame->Y;
}
其中gcalloc
就是指从GC heap上分配一个什么东西,这里分配的是一个map,用于保存foo的stack frame。
然后,foo可以变成这样:
int foo(continuation C) {
foo_stack_frame = gcalloc { C: continuation; X: int; Y: int }
foo_stack_frame->C = C;
foo_stack_frame->X = 4;
continuation Next = { foo2, foo_stack_frame };
return bar(Next, 14);
}
int foo2(continuation C, int Y) {
foo_stack_frame = C->stack_frame;
foo_stack_frame->Y = Y;
continuation Next = foo_stack_frame->C;
return Next->F(Next, foo_stack_frame->X * foo_stack_frame->Y);
}
这里的continuation
你可以认为就是一个“返回地址”,这个等下再说。
你很可能会问,这么一大堆变换到底是在干神马?
嗯,其实这一堆就只变换干了一件事,那就是——把所有的函数调用变成了tail call。
大家都知道,tail call是可以直接优化成goto的,所以我们必须记录下原本的返回地址,把它传递给被tail call的函数,这样它就可以在结束的时候直接返回到最初的调用者那里。
如果对每个函数都做这样的变换,让每个函数调用都是tail call,那我们就彻底干掉了stack,整个程序只用goto就搞定。
PS. 对上面内容有兴趣的童鞋可以去看看 http://en.wikipedia.org/wiki/Continuation-passing_style
PPS. 其实从“理解”这个角度而言,递归往往更容易更清晰,因为它天生就是一个把高复杂度问题分解为低复杂度的过程。
怪我咯2017-04-17 11:26:40
通过栈,所有递归都能变成循环。高级语言只是把语言级别的递归变成实现级别的栈。这个问题在网上有很详细的论文。用google搜recursion iteration会发现相当多的答案。
用栈来实现将递归变成循环并不能带来性能的优化,在同种语言的条件下比较更可能带来性能的损失。
有的递归不需要增加栈。去掉了栈和栈操作可以带来速度和空间两方面的效率提升。这其中的一类叫尾递归,有的语言实现了尾递归优化,也就是自动将尾递归变成循环同时不增加栈。比如lisp,scheme,haskell等很多语言都实现了这种优化,但是大多数动态语言包括python,javascript,ruby都没有,因此这些语言经常会报告递归函数超过最大栈深度的错误。
我正在做的一个语言,其中一个特性是在支持尾递归的优化的同时还要支持某些非尾递归的优化,将递归函数转换成不用栈的循环(当然只是一部分,因为不用栈是不可能将所有函数转换成循环的)。
比如sum = (x) -> if x=0 then return 0 else return x+f(x-1)
这个函数是非尾递归的,因为在递归调用函数之后还做了加法。
转换成尾递归是这种形式:sum = (x, acc) -> if x=0 then return acc else return f(x-1, acc)
现在的某些语言将自动把后面这种函数变成循环,同时栈不增加。也就是类似如下的代码
sum = (x, acc) -> while 1 if x=0 then return acc else acc += x
我做的语言将自动把非尾递归的版本变成如下的循环:
sum = (x) -> sum = 0; while 1 then if x=0 then return sum else x--; f += x
补充说明一点: 我这里提供的并不是伪代码,全部都是我将发布的语言中的合法代码。
大家讲道理2017-04-17 11:26:40
不僅loop能夠代替recursion, 而且還可以根據recursion的公式,直接推倒Genrating Function求值.
比如說Fibonacci, f(n)=f(n-1)+f(n-2),他的closed form就是 F(n) = n / (1-n-n^2)
參考:
http://en.wikipedia.org/wiki/Fibonacci_number
http://www.austinrochford.com/posts/2013-11-01-generating-functions-and-fibonacci-numbers.html
http://www.math.cmu.edu/~af1p/Teaching/Combinatorics/Slides/Generating-Functions.pdf
ringa_lee2017-04-17 11:26:40
递归是一个执行速度特别慢的算法,个人建议,能用递归尽量不用递归。
比如:f(n)=f(n-1)+f(n-2)
当n=100时,程序就跑不起了,,个别说更大的了。。。
大家可以尝试
阿神2017-04-17 11:26:40
吧递归写成循环,需要把函数表达式写成f(x)=x的多项式
形式,简单的递归还行,复杂的递归化简起来相当费劲,已经超出了程序员的力所能及范畴了。