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)
View Code

四、python中的递归效率低且没有尾递归优化

#python中的递归
python中的递归效率低,需要在进入下一次递归时保留当前的状态,在其他语言中可以有解决方法:尾递归优化,即在函数的最后一步(而非最后一行)调用自己,尾递归优化:http://egon09.blog.51cto.com/9161406/1842475
但是python又没有尾递归,且对递归层级做了限制

#总结递归的使用:
1. 必须有一个明确的结束条件

2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少

3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)

 

posted @ 2021-03-31 18:29  猫咪也会码代码  阅读(131)  评论(0)    收藏  举报