写在前面:本人算法小白,出生车辆工程,本科偏机械,硕士入了自动驾驶的坑,对于算法总是停留在一知半解的层次,最近在刷LeetCode,递归函数一直是自己的短板,对于其的理解如下,希望可以帮助到大家。欢迎留言,批评指正!!!
1+2+3+4+…+99+100,不使用递归的话,可以用循环
代码示例:
def sum():
for i in range(1,101):
sum1+=i
return sum1
但是当使用递归函数时:
def sum_number(n):
if n<=0:
return 0
return n+sum_number(n-1)
sum_number(100)
上述两行代码所实现的功能一致,刚刚接触的时候就想,能用循环为什么用这么抽象的东西,但是不然,递归的应用可以让整个函数的求解过程变得更加清晰。
L e i g h C a l d w e l l Leigh Caldwell LeighCaldwell在 S t a c k O v e r F l o w Stack OverFlow StackOverFlow中有过这么一段话:如果使用循环,程序的性能可能更高:如果使用递归,程序可能会更加清晰。
由于递归函数一直调用自己,所以在编写程序时,很容易出错,进而导致无限循环,程序出错。
编写递归函数时,必须告诉他何时停止递归,所以每个递归函数都有两部分构成:基线条件和递归条件。递归条件是递归函数何时调用自己,极限条件是函数不在调用自己,从而避免无限循环。
回头看代码:
def sum(n):
if n < = 0: # 基线条件 递归函数何时停止递归
return 0
else: # 递归条件 何时开始递归运算
sum1=n+sum(n-1)
return sum1
简而言之,递归的核心思想:每一次递归,整体问题都要比原来减小,并且递归到一定层次时,要能直接给出结果!
每一个递归程序都遵循相同的基本步骤:
1.初始化算法。 递归程序通常需要一个开始时使用的种子值(seed value)。可以向函数传递参数,或者提供一个入口函数,这个函数是非递归的,但可以为递归计算设置种子值。
2.检查要处理的当前值是否已经与基线条件相匹配(base case)。如果匹配,则进行处理并返回值。是否已经满足基线条件。
3.使用更小的或更简单的子问题(或多个子问题)来重新定义答案。体现了分治的思想。
4.对子问题运行递归算法。
5. 将结果合并入答案的表达式,返回结果。
递归函数为什么会溢出?
通俗理解: 递归函数在调用自己的时候,还没有退出,占内存,多了肯定会导致内存崩溃。
本质上来讲,在计算机中,函数调用是通过栈( s t a c k stack stack)这样数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会少一层栈帧。由于栈的大小不是无限的,所以,递归调用次数多了,会导致栈溢出。
递归特性
尾递归,进入下一个函数不再需要上一个函数的环境了,得出结果以后直接返回。
执行完一层调用下一层的时候,把这一层的数据给销毁掉,并且下一层和这一层没有关系,叫尾递归。
哈哈哈 很遗憾 Python 不支持尾递归优化!!
Notes:递归函数调用时,参数是一个个进去,满足基线条件后,参数也是一层层出来,递归调用栈!(LIFO)
def zero(n):
n = n // 2
print(n)
if n == 0:
return 'Done'
zero(n)
print(n) # 1 2 5
'''
第一层会n=5,第二层会n=2,第三层会n=1,第四层n=0,符合条件程序停止。停止之后,程序的控制权会回到第三层调用第四层的位置,也就是zero(n),然后print出1,然后回到第二层print出2,最后回到第一层print出5。
整个程序是先一层层进去,然后在一层层出来。
'''
zero(10) # 5 2 1 0 1 2 5