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 匹配任意字符(包括换行符)的正则表达式写法
Oct 29 Python
python文件操作之目录遍历实例分析
May 20 Python
Django自定义认证方式用法示例
Jun 23 Python
python机器学习理论与实战(四)逻辑回归
Jan 19 Python
使用python制作一个为hex文件增加版本号的脚本实例
Jun 12 Python
Python 取numpy数组的某几行某几列方法
Oct 24 Python
python实现将两个文件夹合并至另一个文件夹(制作数据集)
Apr 03 Python
详解python with 上下文管理器
Sep 02 Python
Python 中Operator模块的使用
Jan 30 Python
python 三种方法提取pdf中的图片
Feb 07 Python
Python Pandas数据分析之iloc和loc的用法详解
Nov 11 Python
利用Python实现翻译HTML中的文本字符串
Jun 21 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调用mysql存储过程实例分析
2014/12/29 PHP
php获得客户端浏览器名称及版本的方法(基于ECShop函数)
2015/12/23 PHP
PHP实现从PostgreSQL数据库检索数据分页显示及根据条件查找数据示例
2018/06/09 PHP
js GridView 实现自动计算操作代码
2009/03/25 Javascript
JQuery下的Live方法和$.browser方法使用代码
2010/06/02 Javascript
利用JQuery的load函数动态加载其它页面的内容的实现代码
2010/12/14 Javascript
jsvascript图像处理—(计算机视觉应用)图像金字塔
2013/01/15 Javascript
js实现的捐赠管理完整实例
2015/01/20 Javascript
JS实现方向键切换输入框焦点的方法
2015/08/19 Javascript
webpack常用配置项配置文件介绍
2016/11/07 Javascript
JavaScript队列、优先队列与循环队列
2016/11/14 Javascript
简单几步实现返回顶部效果
2016/12/05 Javascript
微信小程序 获取session_key和openid的实例
2017/08/17 Javascript
微信小程序实现页面浮动导航
2020/01/08 Javascript
为什么JavaScript中0.1 + 0.2 != 0.3
2020/12/03 Javascript
[45:16]完美世界DOTA2联赛PWL S3 Magma vs Phoenix 第一场 12.12
2020/12/16 DOTA
python实现二维插值的三维显示
2018/12/17 Python
啥是佩奇?使用Python自动绘画小猪佩奇的代码实例
2019/02/20 Python
python项目对接钉钉SDK的实现
2019/07/15 Python
Pycharm内置终端及远程SSH工具的使用教程图文详解
2020/03/19 Python
为什么如下的代码int a=100,b=100;long int c=a * b;不能工作
2013/11/29 面试题
优秀应届生推荐信
2013/11/09 职场文书
软件毕业生个人鉴定
2014/03/03 职场文书
《大江保卫战》教学反思
2014/04/11 职场文书
灰雀教学反思
2014/04/28 职场文书
学校评语大全
2014/05/06 职场文书
女生节标语
2014/06/26 职场文书
推普周活动总结
2014/08/28 职场文书
住房抵押登记委托书
2014/09/27 职场文书
简单租房协议书
2014/10/21 职场文书
2014年职称评定工作总结
2014/11/26 职场文书
晚会主持人开场白台词
2015/05/28 职场文书
宣传稿格式范文
2015/07/23 职场文书
2016个人先进事迹材料范文
2016/03/01 职场文书
详解JS WebSocket断开原因和心跳机制
2021/05/07 Javascript
TypeScript 使用 Tuple Union 声明函数重载
2022/04/07 Javascript