递归(英语:Recursion)概述
递归在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。递归一词还较常用于描述以类似方法重复事物的过程。
递归是从所需结果出发,不断回溯前一运算直到回到初值再递推得到所需结果;从未知到已知,从大到小,再从小到大的过程。
递归条件
- 基线条件:确定递归到何时终止,函数不再调用自己,也称为递归出口;
- 递归条件:函数调用自己,将大问题分解为类似的小问题,也称为递归体;
- 每一次递归,整体问题都要比原来减小,并且递归到一定层次时,要能直接给出结果。
递归算法常用来解决结构相似的问题,所谓结构相似,是指构成原问题的子问题与原问题在结构上相似,可以用类似的方法解决。本质上,递归是把一个不能解决或不好解决的大问题转化成一个或几个小问题,再把这些小问题进一步分解成更小的问题,直至每个小问题都可以直接解决。实际上,递归会将前面所有调用的函数暂时挂起,直到递归终止条件给出明确的结果后,才会将所有挂起的内容进行反向计算。
Python默认的递归次数限制为1000,以避免耗尽计算机中的内存
递归应用
- 数据的定义是按递归定义的,比如:斐波那契数列、阶乘等;
- 问题的解法是按递归算法实现的,比如:回溯法;
- 数据的结构形式是按递归定义的,比如:树的遍历、图的搜索等;
常用算法
- 斐波那契数列
def fibonacci(n):
"""斐波那契函数 递归求解"""
if n < 2:
return n
return fibonacci(n - 2) + fibonacci(n - 1)
def fibonacci1(n):
""" 递推求解"""
s, t = 0, 1
while n > 0:
s, t = t, s + t
n -= 1
return s
- 阶乘
fact(n) = n! = 1 * 2 * 3 * 4 * ......* (n-1) * n = n * fact(n-1),需要注意的是0和1的阶乘都是1
def factorial(n):
"""递归 阶乘"""
if n == 0 or n == 1:
return 1
else:
return (n * factorial(n - 1)),
# 尾递归优化阶乘
def fact(n):
return fact_iter(n, 1)
def fact_iter(n, product):
if n == 1:
return product
return fact_iter(n - 1, n * product)
- 快速排序
def quick_sort(n):
if len(n) < 2:
return n
else:
zero = n[0]
left = [x for x in n[1:] if x < zero]
right = [x for x in n[1:] if x > zero]
return quick_sort(left) + [x for x in n if x == n[0]] + quick_sort(right)
尾递归优化
- 在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多时,可能会导致栈溢出;
- 尾递归:指函数返回时调用自身本身,类似循环,并且return语句不能包含表达式。编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况;尾递归是优化递归防止溢出的一种方法,但并不能彻底解决溢出;
- 阶乘的factorial(n)函数,return语句,返回了n * fact(n - 1)的乘法表达式,不是尾递归。要改成尾递归方式,需要把每一步的乘积传入到递归函数中,如fact_iter函数,返回函数前先把结果计算出来,不会影响函数调用。
总结
递归使代码看起来更整洁优雅,可以将复杂问题分解成简单的子问题。但是递归逻辑很难调试,而且运行效率较低,且调用次数太多容易造成栈溢出;
快速排序基于分治法,将大问题分解为若干个子问题,正好通过递归处理;
人生苦短,我用python!