首页
学习
活动
专区
圈层
工具
发布
30 篇文章
1
【愚公系列】2021年12月 Python教学课程 01-全方位解读Python
2
【愚公系列】2021年12月 Python教学课程 02-变量
3
【愚公系列】2021年12月 Python教学课程 03-数据类型
4
【愚公系列】2021年12月 Python教学课程 04-字符串
5
【愚公系列】2021年12月 Python教学课程 05-列表List
6
【愚公系列】2021年12月 Python教学课程 06-元组Tuple
7
【愚公系列】2021年12月 Python教学课程 07-字典Dict
8
【愚公系列】2021年12月 Python教学课程 08-基本语法
9
【愚公系列】2021年12月 Python教学课程 09-流程控制-顺序执行
10
【愚公系列】2021年12月 Python教学课程 10-流程控制-条件判断
11
【愚公系列】2021年12月 Python教学课程 11-流程控制-循环控制
12
【愚公系列】2021年12月 Python教学课程 12-Python函数
13
【愚公系列】2021年12月 Python教学课程 13-变量作用域
14
【愚公系列】2021年12月 Python教学课程 14-递归函数
15
【愚公系列】2021年12月 Python教学课程 15-匿名函数
16
【愚公系列】2021年12月 Python教学课程 16-推导式
17
【愚公系列】2021年12月 Python教学课程 17-模块与包
18
【愚公系列】2021年12月 Python教学课程 18-异常处理
19
【愚公系列】2021年12月 Python教学课程 19-面向对象编程-面向对象定义
20
【愚公系列】2021年12月 Python教学课程 20-面向对象编程-类和对象
21
【愚公系列】2021年12月 Python教学课程 21-面向对象编程-特殊成员和魔法方法
22
【愚公系列】2021年12月 Python教学课程 22-面向对象编程-封装、继承、多态
23
【愚公系列】2021年12月 Python教学课程 23-面向对象编程-运算符重载
24
【愚公系列】2021年12月 Python教学课程 24-Python数据库编程
25
【愚公系列】2021年12月 Python教学课程 25-Python文件读写
26
【愚公系列】2021年12月 Python教学课程 26-系统编程
27
【愚公系列】2021年12月 Python教学课程 27-算法
28
【愚公系列】2021年12月 Python教学课程 28-Web开发基础
29
【愚公系列】2021年12月 Python教学课程 29-MVC设计模式
30
【愚公系列】2021年12月 Python教学课程 30-Django简介

【愚公系列】2021年12月 Python教学课程 14-递归函数

文章目录

一、递归函数

我们在前面的章节中,很多次的看到了在函数中调用别的函数的情况。如果一个函数在内部调用了自身,这个函数就被称为递归函数。

What?函数可以自己调用自己?那不是成为了“衔尾蛇”?会不会进入死循环,永远退出不了?我们先看一个例子,典型的高斯求和问题,1+2+3+4+…+99+100,不使用递归的话,我们可以用循环,这么做:

代码语言:javascript
复制
def sum_number(n):
    total = 0
    for i in range(1, n+1):
        total += i
    return total
sum_number(100)

但如果使用递归函数来写,是这样的:

代码语言:javascript
复制
def sum_number(n):
    if n <= 0:
        return 0
    return n+sum_number(n-1)
sum_number(100)

递归最核心的思想是:每一次递归,整体问题都要比原来减小,并且递归到一定层次时,要能直接给出结果!

每一个递归程序都遵循相同的基本步骤:

  • 初始化算法。递归程序通常需要一个开始时使用的种子值(seedvalue)。可以向函数传递参数,或者提供一个入口函数,这个函数是非递归的,但可以为递归计算设置种子值。
  • 检查要处理的当前值是否已经与基线条件相匹配(base case)。如果匹配,则进行处理并返回值。if n<=0:
  • 使用更小的或更简单的子问题(或多个子问题)来重新定义答案。
  • 对子问题运行算法。
  • 将结果合并入答案的表达式。
  • 返回结果。

递归函数的优点是定义简单,代码量少,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

可是,有同学会问,从上面的例子来看,我一点没觉得递归有多简单,反倒更难理解。那么请看下面的例子:

如果有这么一个树形结构的评论系统(博主的博客评论系统):

代码语言:javascript
复制
1--直接对文章的评论
 1.1--对评论 1 的回复
 1.1.1--对评论 1.1 的回复
 1.1.2--对评论 1.1 的回复
 1.1.3--对评论 1.1 的回复
 1.2 --对评论 1 的回复
 1.2.1--对评论 1.2 的回复
 1.3 --对评论 1 的回复 
2--直接对文章的评论
 2.1 --对评论 2 的回复
 2.1.1--对评论 2.1 的回复
 2.2 --对评论 2 的回复
3--直接对文章的评论
4--直接对文章的评论

请一定要注意,其中的 1.1.1 这种是方便大家理解评论层次,并不是真正的评论内容。每一个评论都有一个指向父评论的指针。现在的要求是,将所有的评论,根据评论的关系,放入一个列表内,然后逐一打印出来。需求的关键是我们必须穷举每个评论的子评论。下面我们写一个用循环来实现的伪代码:

代码语言:javascript
复制
lis = []
all_top_comments = ["顶级评论 1","顶级评论 2","顶级评论 3","....."]
for comment in all_top_comments:
    for child_comment in comment:
        for child_child_comment in child_comment:
            for child_child_child_comment in child_child_comment:
            # ....子评论的子评论的子评论的....
            # 很快你就没办法写下去了,这种代码必定会被老板“重视”

你知道评论嵌套层级会有几层?有 100 层你就写 100 个 for 循环?对于这种问题,循环的做法是不行的。但是用递归就很简单了。

代码语言:javascript
复制
lis = []
all_top_comments = ["顶级评论 1","顶级评论 2","顶级评论 3","....."]
def get_comment(comments):
    for comment in comments:
        lis.append(comment)
        child_comments = comment.child() # 假设有一个 child 方法获取当前评论的所有子评论。
        if len(child_comments) > 0: # 如果有子评论的话,就递归查找下去,否则回退
            get_comment(child_comments)
get_comment(all_top_comments)

使用递归函数需要注意防止递归深度溢出,在 Python 中,通常情况下,这个深度是1000 层,超过将抛出异常。在计算机中,函数递归调用是通过栈(stack)这种数据结构实现的,每当进入一个递归时,栈就会加一层,每当函数返回一次,栈就会减一层。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

下一篇
举报
领券