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 相关文章推荐
多线程爬虫批量下载pcgame图片url 保存为xml的实现代码
Jan 17 Python
在Python中使用swapCase()方法转换大小写的教程
May 20 Python
Python随手笔记之标准类型内建函数
Dec 02 Python
python3实现公众号每日定时发送日报和图片
Feb 24 Python
如何优雅地处理Django中的favicon.ico图标详解
Jul 05 Python
python selenium自动上传有赞单号的操作方法
Jul 05 Python
利用selenium爬虫抓取数据的基础教程
Jun 10 Python
Python底层封装实现方法详解
Jan 22 Python
pycharm 对代码做静态检查操作
Jun 09 Python
在Tensorflow中实现leakyRelu操作详解(高效)
Jun 30 Python
python之pygame模块实现飞机大战完整代码
Nov 29 Python
深入浅析python3 依赖倒置原则(示例代码)
Jul 09 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
在“咖啡之国”感受咖啡文化
2021/03/03 咖啡文化
在WAMP环境下搭建ZendDebugger php调试工具的方法
2011/07/18 PHP
简单谈谈PHP面向对象之标识对象
2017/06/27 PHP
PHP封装的XML简单操作类完整实例
2017/11/13 PHP
php 的多进程操作实践案例分析
2020/02/28 PHP
javascript document.referrer 用法
2009/04/30 Javascript
javascript改变position值实现菜单滚动至顶部后固定
2013/01/18 Javascript
document.getElementById获取控件对象为空的解决方法
2013/11/20 Javascript
JavaScript中对象的不同创建方法
2016/08/12 Javascript
js原生跨域_用script标签的简单实现
2016/09/24 Javascript
利用Node.js+Koa框架实现前后端交互的方法
2017/02/27 Javascript
网页中的图片查看器viewjs使用方法
2017/07/11 Javascript
vue的一个分页组件的示例代码
2017/12/25 Javascript
详解Puppeteer 入门教程
2018/05/09 Javascript
详解vue 命名视图
2019/08/14 Javascript
layui 动态设置checbox 选中状态的例子
2019/09/02 Javascript
简洁的十分钟Python入门教程
2015/04/03 Python
为Python的web框架编写前端模版的教程
2015/04/30 Python
Python中的字典与成员运算符初步探究
2015/10/13 Python
浅谈python requests 的put, post 请求参数的问题
2019/01/02 Python
python sklearn库实现简单逻辑回归的实例代码
2019/07/01 Python
解决Python设置函数调用超时,进程卡住的问题
2019/08/08 Python
Python字节单位转换(将字节转换为K M G T)
2021/03/02 Python
django使用多个数据库的方法实例
2021/03/04 Python
利用HTML5中的Canvas绘制一张笑脸的教程
2015/05/07 HTML / CSS
菲律宾票务网站:StubHub菲律宾
2018/04/21 全球购物
神话般的珠宝:Ross-Simons
2020/07/13 全球购物
开学典礼决心书
2014/03/11 职场文书
2014年中学生检讨书大全
2014/10/09 职场文书
街道务虚会发言材料
2014/10/20 职场文书
2016年春节问候语
2015/11/11 职场文书
2016年第32个教师节红领巾广播稿
2015/12/18 职场文书
初中运动会闭幕词范本3篇
2019/12/09 职场文书
MySQL中datetime时间字段的四舍五入操作
2021/10/05 MySQL
分享python函数常见关键字
2022/04/26 Python
使用Python开发贪吃蛇游戏 SnakeGame
2022/04/30 Python