0.递归的概念
概念:递归算法是一种直接或者间接的调用自身算法的过程
以下内容的总结非常重要,刚开始看的时候不以为然,但经过很多案例的总结之后,发现核心的还是这些内容。领悟核心内容,才方便后续举一反三!
特点:
①递归就是在过程或者函数里调用自身。
②在使用递归策略时,必须有一个明确的递归条件,称为递归出口。
③递归算法解题通常显得很简洁,但递归算法解题的效率较低。所以一般不倡导使用递归算法设计程序。
④在递归调用的过程当中系统的每一层的返回点、局部变量等开辟了栈来存储。递归函数次数过多容易造成栈溢出等。所以一般不倡导用递归算法设计程序。
四个条件:
①每次在调用规模上都有所缩小(通常是减半),即从大到小开始计算和或积等
②相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入)
③ 在条件的规模最小值的时候,需要给出直接的结果。因此,每次递归调用都是有条件的(以条件的值未达到最小的值为递归条件)
④无条件的递归调用将会成为死循环而不能正常结束
递归算法一般用于解决三类问题:
(1)数据的定义是按递归定义的。(比如斐波那契数列)
(2)问题解法按递归算法实现。(回溯)
(3)数据的结构形式是按递归定义的。(比如树的遍历,图的搜索,二分法查找等)
1.普通递归
案例一:求1 + 2+ 3+…+n 的和
def cal_sum(n): # n代表从1共计加到n
res = None
if n == 1: # 在递归之前,先确定最小值情况的结果
res = 1
elif n > 1: # 从最大值开始,递减的时候,结果情况
res = n + cal_sum(n-1)
return res
案例二:求3 + 6+ 9+ 12+…+n 的和
求n个如上规律数组的和
- 如上发现,其实是案例二是案例一的一个简单升级,最终要求N个数的和
- 3代表第一个数,6代表第二个数。如果把第N个数看做索引,那每个索引对应的数值就是索引乘以3
def cal_sum(n):
res = None
if n == 1:
res = 3 # n为第一个索引的时候,先确定res的值
elif n > 1:
res = 3*n + cal_sum(n - 1) # 原理同上
return res
- 以上两个案例,每个索引的数值,都只和索引有关系,与前一个数的大小似乎没关系。
- 在第一节部分,总结的四个条件中,第二个条件(前一次要为后一次做准备)没有在如上两个案例中体现,但往往这个部分,才是我们最棘手的问题。
2.斐波那契数列—递归求值
例如,0、1、1、2、3、5、8、13、21、34…这样的数列,第三个数值,是前面两个数值之和,求第N个数的值?
- 以上部分,就会用到我们上面谈到的,前一次或前二次的数据,要为下一个数值做好准备,这样才能层层递进
- 而一般递归都是条件的值,从大到小排序的,所以如果计算第n个数值的大小,使用feibo(n),那么就需要计算feibo(n-1) + feibo(n-2)的和。而计算feibo(n-2)的值,就需要计算feibo(n-3)+fiebo(n-4)的和
- 依次往复,知道我们能知道的feibo(1) 和feibo(2)的值
def feibo(n):
n1 = 0
n2 = 1
last = None
if n == 1:
last = n1
elif n == 2:
last = n1 + n2
elif n >= 3:
last = feibo(n-1) + feibo(n-2) # 此处容易产生很大的开销,特别是递归深度很深的时候
return last
- 递归相对运行效率低,每一次调用函数都要开辟栈帧。
- 递归调用的深度不宜过深,python对递归深度做了限制,以保护解释器,超过递归深度限制,抛出RecursionError。sys.getrecursionlimit()
3.斐波那契数列—循环求值
循环方式求值:
def feibo_num(n):
my_list = list()
n1 = 0
n2 = 1
if n == 1:
my_list.append(n1)
elif n == 2:
my_list.append(n1)
my_list.append(n2)
elif n > 2:
i = 2 # 因为n1, n2已经确定了,需要从第三个数开始求值,所以设置i=2
my_list.append(n1)
my_list.append(n2)
while (i < n):
last = n1 + n2
n1, n2 = n2, last # 要求得last的值,n1, n2需要层层递进,然后才有last的值
my_list.append(last)
i += 1
return my_list
循环方式求值,这种方式内存开销要小很多