Python理解递归的方法总结


Posted in Python onJanuary 28, 2019

递归

一个函数在执行过程中一次或多次调用其本身便是递归,就像是俄罗斯套娃一样,一个娃娃里包含另一个娃娃。

递归其实是程序设计语言学习过程中很快就会接触到的东西,但有关递归的理解可能还会有一些遗漏,下面对此方面进行更加深入的理解

递归的分类

这里根据递归调用的数量分为线性递归、二路递归与多重递归

线性递归

如果一个递归调用最多开始一个其他递归调用,我们称之为线性递归。

例如:

def binary_search(data, target, low, high):
  """
  二分查找,对有序列表进行查找,如果找到则返回True,否则返回False 
  """
 
  if low > high:
    return False
  else:
    mid = (low + high) // 2
    if target == data[mid]:
      return True
    elif target < data[mid]:
      return binary_search(data, target, low, mid - 1)
    else:
      return binary_search(data, target, mid + 1, high)

虽然在代码中有两个binary_search,但他们是不同条件执行的,每次只能执行一次,所以是线性递归。

二路递归

如果一个递归调用可以开始两个其他递归调用,我们称之为二路递归

例如:

def binary_sum(S, start, stop):
  """
  二路递归计算一个序列的和,例如S[0:5],就像切片的范围一样
 
  """
 
  if start >= stop:
    return 0
  elif start == stop - 1:
    return S[start]
  else:
    mid = (start + stop) // 2
    return binary_sum(S, start, mid) + binary_sum(S, mid, stop)

这个递归每次执行都会调用两次该函数,所以说是二路递归,每次递归后,范围缩小一半,所以该递归的深度是1+logn

多重递归

如果一个递归调用可以开始三个或者更多其他递归调用,我们称之为多重递归

例如:

import os
 
def disk_usage(path):
  """
  计算一个文件系统的磁盘使用情况,
 
  """
 
  total = os.path.getsize(path)
  if os.path.isdir(path):
    for filename in os.listdir(path):
      childpath = os.path.join(path, filename)
      total += disk_usage(childpath)
  print('{0:<7}'.format(total), path)
  return total

os.path.getsize为获得标识的文件或者目录使用的即时磁盘空间大小。我理解的是如果该标识的是一个文件,那么就是获得该文件的大小,如果是一个文件夹的话,那就是获得该文件夹的大小,但不包括文件夹里边的内容,就像是一个盒子中放了很多物品,但这里只计算了盒子的重量,但没有计算物品的重量,也就是计算了一个空盒子。所以这个递归函数中的递归调用次数取决于这一层文件或文件夹的数量,所以是多重递归。

递归的不足

递归的不足显然就是时间与空间的消耗 ,这篇文章中使用了缓存的方法减少了斐波那契数列的计算消耗,在这里我们使用另一种方式来改善那种坏的递归:

def fibonacci(n):
  """
  斐波那契数列计算,返回的是一个元组
 
  """
 
  if n <= 1:
    return (n, 0)
  else:
    (a, b) = fibonacci(n - 1)
    return(a + b, a)

将原来的二路递归改为了线性递归,减少了重复的计算。

python的最大递归深度

每一次递归都会有资源的消耗,每一次连续的调用都会需要额外的内存,当产生无限递归时,那就意味着资源的迅速耗尽,这明显是不合理的。python为了避免这种现象,在设计时有意的限制了递归的深度,我们可以测试一下

def limitless(n):
  print('第' + str(n) + '次调用')
  n += 1
  return limitless(n)
limitless(1)

第988次调用
第989次调用
第990次调用
第991次调用
第992次调用
第993次调用
第994次调用
第995次调用
第996次调用
Traceback (most recent call last):
File “D:/github/Data-Structure/code/递归.py”, line 73, in
limitless(1)
File “D:/github/Data-Structure/code/递归.py”, line 70, in limitless
return limitless(n)
File “D:/github/Data-Structure/code/递归.py”, line 70, in limitless
return limitless(n)
File “D:/github/Data-Structure/code/递归.py”, line 70, in limitless
return limitless(n)
[Previous line repeated 992 more times]
File “D:/github/Data-Structure/code/递归.py”, line 68, in limitless
print(‘第' + str(n) + ‘次调用')
RecursionError: maximum recursion depth exceeded while calling a Python object

最终递归到996次停止了递归,也就是python的递归深度限制在了1000附近。

1000层的限制已经是足够的了,但是还是有可能限制到某些计算,所以python提供了一个修改限制的方

import sys
def limitless(n):
  print('第' + str(n) + '次调用')
  n += 1
  return limitless(n)
 
print(sys.getrecursionlimit())#1000
sys.setrecursionlimit(2000)
limitless(1)
第1994次调用
第1995次调用
第1996次调用
Traceback (most recent call last):
File “D:/github/Data-Structure/code/递归.py”, line 70, in limitless
return limitless(n)
File “D:/github/Data-Structure/code/递归.py”, line 70, in limitless
return limitless(n)
File “D:/github/Data-Structure/code/递归.py”, line 70, in limitless
return limitless(n)
[Previous line repeated 995 more times]
File “D:/github/Data-Structure/code/递归.py”, line 68, in limitless
print(‘第' + str(n) + ‘次调用')
RecursionError: maximum recursion depth exceeded while calling a Python objec

可见把这个深度该为2000后便多了1000次调用,但这个深度显然不是设置多少就是多少,毕竟还有计算机CPU与内存的限制,比如吧深度改为10000,那么运行后

第3920次调用
第3921次调用
第3922次调用
第3923次调用

Process finished with exit code -1073741571 (0xC00000FD)

到达3923次便终止了,查询-1073741571发现是递归栈溢出的问题。

尾递归

如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。

Python解释器在对于一次函数调用中,会使用一个栈帧来保存当前调用的函数的信息,如输入参数、返回值空间、计算表达式时用到的临时存储空间、函数调用时保存的状态信息以及输出参数。因此在递归的调用中,这种未执行完的函数会一层一层的占用大量的栈帧。如果将递归的调用放到函数执行的最后一步,那么执行完这步,该次函数的栈帧就会释放,调用函数的新栈帧就会替换掉之前的栈帧,所以无论调用的深度有多少次,都只会占用一个栈帧,那也就不会发生栈溢出的问题。这就是尾递归。

所以根据需要,尾递归必须是线性递归,并且递归调用的返回值必须立即返回。

拿一个阶乘递归函数举例

def factorial(n):
  """
  阶乘递归函数
 
  """
  if n == 0:
    return 1
  else:
    return n * factorial(n - 1)

上边这个是一个普通的递归,下面把他改成尾递归的形式

def facttail(n, res):
  """
  阶乘尾递归,res初始为1
 
  """
 
  if n < 0:
    return 0
  elif n == 0:
    return 1
  elif n == 1:
    return res
  else:
    return facttail(n - 1, n *res)

这个函数比之前那个还多了个res,第一种每次调用完要乘n,这里的res就起了相同的作用,由于尾递归每一层的栈帧要释放,所以通过res来作为相乘的过程。我个人认为尾递归的难度就在于参数的设计,因为它的前提条件就是调用后什么也不再执行了,所以要作为传递的东西就得提前通过参数设计传递,总之要想设计一个尾递归的算法还是需要好好思考一下的。

Python 相关文章推荐
布同自制Python函数帮助查询小工具
Mar 13 Python
使用Python脚本在Linux下实现部分Bash Shell的教程
Apr 17 Python
Python实现字典依据value排序
Feb 24 Python
Python 实现引用其他.py文件中的类和类的方法
Apr 29 Python
Django csrf 两种方法设置form的实例
Feb 03 Python
pytorch中torch.max和Tensor.view函数用法详解
Jan 03 Python
Java Spring项目国际化(i18n)详细方法与实例
Mar 20 Python
python 对一幅灰度图像进行直方图均衡化
Oct 27 Python
详解python中的异常捕获
Dec 15 Python
从Pytorch模型pth文件中读取参数成numpy矩阵的操作
Mar 04 Python
用Python远程登陆服务器的步骤
Apr 16 Python
聊聊Python String型列表求最值的问题
Jan 18 Python
代码详解django中数据库设置
Jan 28 #Python
Python控制键盘鼠标pynput的详细用法
Jan 28 #Python
用python 实现在不确定行数情况下多行输入方法
Jan 28 #Python
对python3中, print横向输出的方法详解
Jan 28 #Python
Python删除n行后的其他行方法
Jan 28 #Python
python 在指定范围内随机生成不重复的n个数实例
Jan 28 #Python
Python实现统计英文文章词频的方法分析
Jan 28 #Python
You might like
PHP的构造方法,析构方法和this关键字详细介绍
2013/10/22 PHP
Laravel 4.2 中队列服务(queue)使用感受
2014/10/30 PHP
PHP自带ZIP压缩、解压缩类ZipArchiv使用指南
2015/03/03 PHP
双冒号 ::在PHP中的使用情况
2015/11/05 PHP
php实现微信支付之退款功能
2018/05/30 PHP
php实现在线考试系统【附源码】
2018/09/18 PHP
js判断两个日期是否相等的方法
2013/09/10 Javascript
点击页面其它地方隐藏该div的两种思路
2013/11/18 Javascript
让浏览器崩溃的12行JS代码(DoS攻击分析及防御)
2016/10/10 Javascript
详解React-Todos入门例子
2016/11/08 Javascript
bootstrap suggest下拉框使用详解
2017/04/10 Javascript
bootstrap轮播图示例代码分享
2017/05/17 Javascript
Vue单文件组件基础模板小结
2017/08/10 Javascript
网页爬虫之cookie自动获取及过期自动更新的实现方法
2018/03/06 Javascript
vue 做移动端微信公众号采坑经验记录
2018/04/26 Javascript
通过nodejs 服务器读取HTML文件渲染到页面的方法
2018/05/17 NodeJs
pm2发布node配置文件ecosystem.json详解
2019/05/15 Javascript
Javascript异步编程async实现过程详解
2020/04/02 Javascript
Python使用reportlab将目录下所有的文本文件打印成pdf的方法
2015/05/20 Python
Python的Django框架中TEMPLATES项的设置教程
2015/05/29 Python
Python实现爬取百度贴吧帖子所有楼层图片的爬虫示例
2018/04/26 Python
Python爬虫获取图片并下载保存至本地的实例
2018/06/01 Python
对python条件表达式的四种实现方法小结
2019/01/30 Python
python导入pandas具体步骤方法
2019/06/23 Python
500行代码使用python写个微信小游戏飞机大战游戏
2019/10/16 Python
Numpy之将矩阵拉成向量的实例
2019/11/30 Python
解决TensorFlow训练内存不断增长,进程被杀死问题
2020/02/05 Python
conda安装tensorflow和conda常用命令小结
2021/02/20 Python
美国受信赖的教育产品供应商:Nest Learning
2018/06/14 全球购物
建筑工程专业学生的自我评价
2013/12/25 职场文书
化妆师职业生涯规划书
2014/02/16 职场文书
车间机修工岗位职责
2014/02/28 职场文书
暑期社会实践先进个人主要事迹
2014/05/22 职场文书
注册资产评估专业求职信
2014/07/16 职场文书
解析python中的jsonpath 提取器
2022/01/18 Python
Python使用华为API为图像设置多个锚点标签
2022/04/12 Python