python函数递归基础
一、什么是函数递归
我们之前讲过函数可以嵌套定义与调用,如果一个函数在函数体代码中调用了自己,这就是递归。
def f1(): print("hhhh") f1() # 这里定义阶段就在函数体中调用了自己,是递归函数 f1()
此时函数的运行顺序是:
#调用f1() # 打印"hhhh" # # 遇到f1(),回过头再次调用f1() # # 打印"hhhh" # # 遇到f1(),回过头再次调用f1() # # 打印"hhhh" # # 遇到f1(),回过头再次调用f1() # # 打印"hhhh" # # 遇到f1(),回过头再次调用f1() # # 打印"hhhh" # # 遇到f1(),回过头再次调用f1() # # ......
函数可以相互调用,间接调用自己
# 间接调用 def f1(): print("from f2") f2() def f2(): print("from f1") f1() f1() # 调用f1() # 打印from f2 # 遇到f2(),调用f2() # 打印from f1 # 遇到f1() # 调用f1() # 打印from f2 # 遇到f2(),调用f2() # 打印from f1 # 遇到f1() # 调用f1() # ......
可以看到,两种情况下,函数递归调用都是无限循环,一旦运行,python应该是无限循环下去,但是python对递归深度做了限制,一旦递归次数超过了1000次,将会报错;这中设置是合理的,因为无限制的递归通常是无用的,我们在定义递归函数时,应该设置某种停止条件
import sys x = sys.getrecursionlimit() # 查看python的递归深度,默认值是1000, print(x) # 可以对深度进行修改,但是一般没有必要 # sys.setrecursionlimit(2000)
二、函数递归的两个阶段:回溯与递推
需求:现有5个同学,需要知道他们的数学成绩,询问第一个同学回答说:他的成绩比第二名同学多5分,第二名同学说比第三名同学多5分,第三名同学说比第四名同学都5分,第四名同学说比第五名同学多5分,第五名同学说考了70分,那么请问,怎样求得每位同学的成绩呢?
以第一位同学为例:
# 需求 # 现有5个同学,需要知道他们的数学成绩, # 询问第一个同学回答说:他的成绩比第二名同学多5分, # 第二名同学说比第三名同学多5分, # 第三名同学说比第四名同学都5分, # 第四名同学说比第五名同学多5分, # 第五名同学说考了70分, # 那么请问,怎样求得每位同学的成绩呢? # 以f(n)代表第n为同学的成绩 f(5) = f(4) + 5 ###### f(4) = f(3) + 5 #回溯# f(3) = f(2) + 5 #过程# f(2) = f(1) + 5 ###### f(1) = 70 # 总结规律 if n == 1: f(1) = 70 else: f(n) = f(n-1) + 5 # 计算结果 f(1) = 70 f(2) = f(1) + 5 = 75 ###### f(3) = f(2) + 5 = 80 #递推# f(4) = f(3) + 5 = 85 #过程# f(5) = f(4) + 5 = 90 ######
我们可以看到,这个案例中,除却最后一位同学,其余同学的成绩都依赖于前一个人的成绩,我们按照f(n) = f(n-1) + 5的规律往前推,直到找到f(1) = 70,以上就是回溯;知道了最后一位同学的成绩,于是第二个、第三个、第四个、第五个都知道了分数,这就是递推
实例讲解:现有一个列表,需要我们打印列表中的每一个元素
list1 = [1,[2,[3,[4,[5,[6,[7,[8,9],5],3],3],23],5],45],34] # 思路分析 # 遍历列表中的元素判断: # 如果不是列表类型,就打印该元素, # 如果是列表,就再次遍历: # 如果不是列表类型,就打印该元素, # 如果是列表,就再次遍历 # ....... # 直到最后一个元素也打印完毕 def list_print(lis): for i in lis: if type(i) is list: # 这一步是关键,如果是列表,我们还要进行遍历判断的过程 # 就像再次创建一个一模一样的函数,于是,在这里调用他自己 list_print(i) else: print(i) list_print(list1) # 传入列表参数
三、二分法应用
现有一个列表,需要自行设计函数判断元素是否在列表中,不适用in等判断方法

list1 = [1, 23, 344, 45, 34, 2, 6, 8, 454, 34, 4, 63, 343] # 分析 # 使用二分法,将列表进行排序后,取出中间的值 # 将目标值和中间值判断,如果目标值大于中间值,说明目标值在大于中间值——最大值的区间 # 切片取到中间值——最大值的列表,再取中间值,以此类推 # 如果第一次比较目标值小于中间值,则往前取 # 不断:二分——判断——二分... # 如果目标值存在列表中,会被定位到,如果不存在,最后只剩空列表 # 实现 def find_num(number,lis): lis.sort(reverse=False) # 因为需要比大小,所以这里需要从小到大排序列表 print(lis) x = len(lis) // 2 # 取中间值的索引 if lis == []: # 如果列表被切成空列表也没有匹配,说明不在列表 return "不在列表" if number > lis[x]: lis = lis[x+1:] find_num(number,lis) elif number < lis[x]: lis = lis[:x] find_num(number,lis) else: print("{}在列表中".format(number)) find_num(45,list1)
四、python中的递归效率低且没有尾递归优化
#python中的递归 python中的递归效率低,需要在进入下一次递归时保留当前的状态,在其他语言中可以有解决方法:尾递归优化,即在函数的最后一步(而非最后一行)调用自己,尾递归优化:http://egon09.blog.51cto.com/9161406/1842475 但是python又没有尾递归,且对递归层级做了限制 #总结递归的使用: 1. 必须有一个明确的结束条件 2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少 3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)