Python 函数进阶-递归函数

递归函数

什么是递归函数

如果一个函数,可以自己调用自己,那么这个函数就是一个递归函数。

递归,递就是去,归就是回,递归就是一去一回的过程。

在这里插入图片描述

递归函数的条件

一般来说,递归需要边界条件,整个递归的结构中要有递归前进段递归返回段。当边界条件不满足,递归前进,反之递归返回。就是说递归函数一定需要有边界条件来控制递归函数的前进和返回。

定义一个简单的递归函数

# 定义一个函数
def recursion(num):
	
    print(num)
	if num == 0:
		return 'ok'
	
    # 这个函数在自己的作用域中调用自己,这个函数就是一个递归函数
	recursion(num-1)


recursion(10)
"""
结果:
10
9
8
7
6
5
4
3
2
1
0
"""

代码解析

我们执行这个函数,参数给了一个10,但是这个函数执行的过程中,又调用了自己,所以现在这个函数就会进入先执行第二次调用自己的过程中,第一次的调用就暂时的阻断了;

第二次调用的时候,num-1,参数就变成了9,继续执行,然后就又执行到了调用自己的代码中,现在就会执行第三次调用的过程中,第二次的调用也阻断了;

这就是 递 的过程;

…………

第十一次调用的时候,num-1,层层的嵌套,参数就变成了0,就符合了作用域中的if num == 0的条件判断式,第十一次的调用就没有再执行到了调用自己的代码,而是彻彻底底的执行完成了 ,然后代码的执行就又回到了第十次的函数调用中。

第十次的函数调用阻断的时候是执行到了recursion(num-1),但是这一行代码执行完了,也就是第十一次调用执行完了,并且后面也没有任何代码,所以第十次调用也结束了,然后就回到了第九次调用;然后第八次;再就是第七次,一直到第一次结束,整个函数的执行就结束了;

这就是 归 的过程。

在这里插入图片描述

内存栈区堆区

栈区空间就是我们常说的栈,栈是一个有去有回,先进后出后出的空间,就像我们上面的例子中讲的,我们最先执行的是函数的第一次调用,但是第一次调用却是最后执行释放掉的,而第十一次调用是最后调用,最先执行释放掉的,这就是先进后出。与栈空间概念相违背的是队列空间,队列空间是一个有去有回,先进先出的空间,就像我们平时排队一样,先排队的会比后来的人先买到票,之后我们学习并发的时候,我们会详细的讲述队列的概念。

单独的讲栈堆就是一种数据结构,栈是先进后出的一种数据结构,堆是排序后的一种树状数据结构。

栈区堆区是内存空间,栈区就是按照先进后出的数据结构,无论创建或销毁都是自动为数据分配内存,释放内存,这是系统自动创建的;堆区就是按照排序后的树状数据结构,可优先取出必要的数据,无论创建或者销毁都是手动分配内存,释放内存,这是编程工作者手动编程出来的。

内存空间 特点
内存中的栈区 自动分配,自动释放
内存中的堆区 手动分配,手动释放

运行程序时在内存中执行,会因为数据类型的不同而在内存的不同区域运行,因不同语言对内存划分的机制不一,当大体来说,有如下四大区域:

  1. 栈区:分配局部变量空间;
  2. 堆区:是用于手动分配程序员申请的内存空间;
  3. 静态区:又称之为全局栈区,分配静态变量,全局变量空间;
  4. 代码区:又称之为只读区、常量区,分配常量和程序代码空间;

上面的栈区、读取、静态区、代码区都只是内存中的一段空间。

死递归

所以我们在使用递归函数的时候要注意,递归函数调用的过程就是一个开辟栈和释放栈的过程,调用的时候开辟栈空间,结束的时候释放栈空间,所以说,如果开辟的空间不结束就会一直存在,就会一直占用内存空间,但是栈空间是一个先进后出的空间,如果新开启的空间不释放掉,之前的空间也不会释放掉的,那么如果我们开辟的空间很多很多,但是又释放不掉,那么我们弱小的内存是否有足够的空间容纳得下这么多的栈呢?如果容纳不下,那么我们的计算机就会爆炸,所以我们在使用递归的时候要注意避免这种情况。尤其是死递归。

每次调用函数时,在内存宗都会单独开辟一个空间,配合函数运行,这个空间叫做栈帧空间。

1、递归是一去一回的过程,调用函数时,会开辟栈帧空间,函数执行结束之后,会释放栈帧空间,递归实际上就是不停地开辟和释放栈帧空间的过程,每次开辟栈帧空间,都是独立的一份,其中的资源不共享。

2、触发回的过程当最后一层栈帧空间全部执行结束的时候,会触底反弹,回到上一层空间的调用处,遇到return,会触底反弹,回到上一层的调用处

3、写递归时,必须给予递归跳出的条件,否则会发生内存溢出,可能会出现死机的情况,所以当递归的层数过多的时候,不建议使用递归。

Python中的环境递归的层数默认为1000层左右,一般都是996层。

# 下面的递归函数没有跳出递归的条件,所以是一个死递归,执行看,是不是1000左右。
def recursion(num):
	print(num)
	recursion(num+1)

recursion(1)

尾递归

简单的来说,在函数返回的时候,调用其本身,并且return语句不包含表达式,这样的一个递归函数就是一个尾递归函数。

换句话说返回的东西就是函数本身就是尾递归函数,而递归函数只是自身调用了自身而已。

一般情况下,尾递归的计算在参数中完成。

我们开始的举例是一个尾递归函数吗?不是,因为那个例子这是调用了自己本省,但是并没有返回,所以还是一个普通的递归函数而已。

使用递归的时候,我们通常在一些技术博客上看到一些关于尾递归优化的东西,这是因为尾递归无论调用多少次函数,都只会占用一份空间,只开辟一个栈帧空间,但是目前 cpython 不支持,然而最常见的解释器就是 cpython 。

Python常见的解释器:cpython、pypy、jpython。

尾递归相比普通递归的优点就是返回值不需要额外过多的运算。

实例

阶乘计算

一个正整数的阶乘是所有小于及等于该数的正整数的积,并且0的阶乘为1。

""" 循环计算 """
def factorial(num):
   if num == 0:
      return 1
   elif num < -1:
      return '只能是零和正整数'
   count = 1
   for i in range(1, num+1):
      count *= i
   return count

res = factorial(5)
print(res)  # 120


""" 使用普通递归 """
def factorial(num):
   if num == 0:
      return 1
   elif num < -1:
      return '只能是零和正整数'
   elif num == 1:
      return num
   return num * factorial(num-1)   # 普通函数返回的是一个表达式

res = factorial(5)
print(res)  # 120


""" 使用尾递归 """
def factorial(num, count=1):
   if num == 0:
      return 1
   elif num < -1:
      return '只能是零和正整数'
   elif num == 1:
      return count
   return factorial(num-1, count*num)   # 尾递归返回的是一个函数,计算式在参数中运算

res = factorial(5)
print(res)  # 120

斐波那契数列

斐波那契数列是以0、1两个数开头,从这个数列从第3个数开始,每一个数都等于前两树之和。

# 使用循环解决
def fibonacci(num):
   x, y = 0, 1

   if num < 1:
      return '输入大于0的数字'
   elif num == 1:
      return 0
   elif num == 2:
      return 1

   for _ in range(num-2):
      x, y = y, y+x
   return y

res = fibonacci(9)
print(res)  # 21


""" 使用普通递归 """
def fibonacci(num):
   if num < 1:
      return '输入大于0的数字'
   elif num == 1:
      return 0
   elif num == 2:
      return 1

   return fibonacci(num-1) + fibonacci(num-2)

res = fibonacci(9)
print(res)  # 21


""" 使用尾递归 """
def fibonacci(num, x=0, y=1):
   if num < 1:
      return '输入大于0的数字'
   elif num == 1:
      return x
   elif num == 2:
      return y

   return fibonacci(num-1, x=y,  y=(x+y))

res = fibonacci(9)
print(res)  # 21

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Python 函数进阶-递归函数 - Python技术站

(0)
上一篇 2023年4月2日
下一篇 2023年4月2日

相关文章

  • python常用标准库(压缩包模块zipfile和tarfile)

    常用的标准库 在我们常用的系统windows和Linux系统中有很多支持的压缩包格式,包括但不限于以下种类:rar、zip、tar,以下的标准库的作用就是用于压缩解压缩其中一些格式的压缩包。 zip格式 import zipfile zipfile模块操作压缩包使用ZipFile类进行操作,使用方法和open的使用方法很相似,也是使用r、w、x、a四种操作模…

    Python开发 2023年4月2日
    00
  • python常用内置函数和关键字

    常用内置方法 在Python中有许许多多的内置方法,就是一些Python内置的函数,它们是我们日常中经常可以使用的到的一些基础的工具,可以方便我们的工作。 查看所有的内置类和内置方法 # 方法一 built_list = dir(__builtins__) # 方法二 import builtins built_list = dir(builtins) 其中…

    Python开发 2023年4月2日
    00
  • python常用标准库(os系统模块、shutil文件操作模块)

    常用的标准库 系统模块 import os 系统模块用于对系统进行操作。 常用方法 os模块的常用方法有数十种之多,本文中只选出最常用的几种,其余的还有权限操作、文件的删除创建等详细资料可以参考官方文档。 system — 执行系统命令 参数的数据类型是字符串格式,内容是系统指令。执行时,直接返回系统输出。 import os os.system(‘ifc…

    Python开发 2023年4月2日
    00
  • Python 函数进阶-高阶函数

    高阶函数 什么是高阶函数 高阶函数就是能够把函数当成参数传递的函数就是高阶函数,换句话说如果一个函数的参数是函数,那么这个函数就是一个高阶函数。 高阶函数可以是你使用def关键字自定义的函数,也有Python系统自带的内置高阶函数。 自定义一个高阶函数 我们下面的例子中,函数 senior 的参数中有一个是函数,那么senior就是一个高阶函数;函数 ten…

    Python开发 2023年4月2日
    00
  • Python 函数进阶-全局空间和局部空间

    全局空间和局部空间 命名空间 命名空间的概念的提出是为了划分和控制变量是否可见,以及生存周期的长短;命名空间的作用范围叫做作用域。 划分一块区域保存所有数据,以字典的方式存储(变量与值形成映射关系)。一共三种。 内建命名空间: 解释器启动时创建,直到解释器运行结束,生存周期最长; 全局命名空间: 文件运行时创建,直到解释器运行结束,生存周期较长; 局部命名空…

    Python开发 2023年4月2日
    00
  • python常用标准库(math数学模块和random随机模块)

    常用的标准库 数学模块 import math ceil — 上取整 对一个数向上取整(进一法),取相邻最近的两个整数的最大值。 import math res = math.ceil(4.1) print(res) # 5 floor — 下取整 对一个数向下取整(退一法),取相邻最近的两个整数的最小值。 import math res = math.…

    Python开发 2023年4月2日
    00
  • python生成器

    生成器 我们学习完推导式之后发现,推导式就是在容器中使用一个for循环而已,为什么没有元组推导式? 原因就是“元组推导式”的名字不是这样的,而是叫做生成器表达式。 什么是生成器 生成器表达式本质上就是一个迭代器,是定义迭代器的一种方式,是允许自定义逻辑的迭代器。生成器使用generator表示。 迭代器和生成器的区别 迭代器本身是系统内置的, 无法重写内置的…

    Python开发 2023年4月2日
    00
  • Python 函数进阶-lambda匿名函数和三元运算符

    匿名函数 什么是匿名函数 用一句话表达只有返回值的函数就是匿名函数。 匿名函数只用来实现一些简单的函数功能,所以追求代码的简洁和高效。 使用关键字 lambda 定义,所以匿名函数又称之为lambda表达式。 分类 无参数的 lambda 表达式 # 普通函数 def func(): return ‘hello motherland’ # 调用 res = …

    Python开发 2023年4月2日
    00
合作推广
合作推广
分享本页
返回顶部