递归 和 尾递归
一、生活中的例子:
场景:问路
递归
问题:天安门怎么走?(等待回答)
A:左拐。/ 接下来怎么走不知道了,你等下,我去问B (A等待B的回答)
B:右拐。/ 接下来怎么走不知道了,你等下,我去问C (B等待C的回答)
C:左拐。/ 接下来怎么走不知道了,你等下,我去问D (C等待D的回答)
D:直行就到了。
* 提问者 → A → B → C → D → C → B → A → 提问者
尾递归
问题:天安门怎么走?给你个小纸条,帮我写在上面(等待回答)
A:写上 “左拐”。/ 接下来把纸条给B (A空闲了)
B:写上 “右拐”。/ 接下来把纸条给C (B空闲了)
C:写上 “左拐”。/ 接下来把纸条给D (C空闲了)
D:写上 “直行就到了”。(把纸条直接给提问者)
* 提问者 → A → B → C → D → 提问者
二、代码实例
递归
# python def foo(x): if x == 1: return x else: return x * foo(x - 1)
// js function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } factorial(5) // 120
执行过程
===> foo(5)
===> 5 * foo(4)
===> 5 * (4 * foo(3))
===> 5 * (4 * (3 * foo(2)))
===> 5 * (4 * (3 * (2 * foo(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120
尾递归
# python def foo_iter(num, product): if num == 1: return product return foo_iter(num - 1, num * product)
// js function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5, 1) // 120
执行过程
===> foo_iter(5, 1)
===> foo_iter(4, 5)
===> foo_iter(3, 20)
===> foo_iter(2, 60)
===> foo_iter(1, 120)
===> 120
三、总结
1.递归(英语:Recursion),在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。
2.递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
3.尾递归是一种形式,只是用这种形式表达出的概念可以被默写编译器优化,尾递归的特殊形式决定了这种递归代码在执行过程中是可以不需要回溯的(通常递归都是需要回溯的)。
如果编译器针对尾递归形式的递归代码作了这种优化,就可能把原本需要线性复杂度栈内存空间O(n)的执行过程用常数复杂度的空间O(1)完成。
4.递归调用可以分为 直接递归调用 和 间接递归调用。例如:在函数a(或过程)中直接引用(调用)函数a本身就是直接递归调用。在函数a(或过程)中调用另外一个函数b,而该函数b又引用(调用)了函数a就是间接递归调用。
5.每个递归函数都有两个条件:基线条件和递归条件。
6.栈有两种操作:压入和弹出。所有函数调用都进入调用栈。 调用栈可能很长,这将占用大量的内存。
四、参考
https://zh.wikipedia.org/wiki/%E9%80%92%E5%BD%92 ,from 维基百科
- https://www.liaoxuefeng.com/wiki/1016959663602400/1017268131039072 , from 廖雪峰官方网站
- https://www.zhihu.com/question/20761771/answer/20672305,from 知乎
- 《算法图解》