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 相关文章推荐
读写json中文ASCII乱码问题的解决方法
Nov 05 Python
使用python进行文本预处理和提取特征的实例
Jun 05 Python
Python数据预处理之数据规范化(归一化)示例
Jan 08 Python
Python基本socket通信控制操作示例
Jan 30 Python
利用python-docx模块写批量生日邀请函
Aug 26 Python
python实现音乐播放器 python实现花框音乐盒子
Feb 25 Python
python 用opencv实现图像修复和图像金字塔
Nov 27 Python
Ubuntu16安装Python3.9的实现步骤
Dec 15 Python
python中的列表和元组区别分析
Dec 30 Python
Python超简单容易上手的画图工具库推荐
May 10 Python
Qt自定义Plot实现曲线绘制的详细过程
Nov 02 Python
实操Python爬取觅知网素材图片示例
Nov 27 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 数组使用详解 推荐
2011/06/02 PHP
PHP实现的构造sql语句类实例
2016/02/03 PHP
PHP中spl_autoload_register()函数用法实例详解
2016/07/18 PHP
Zend Framework数据库操作方法实例总结
2016/12/11 PHP
thinkPHP中U方法加密传递参数功能示例
2018/05/29 PHP
php-fpm.conf配置文件中文说明详解及重要参数说明
2018/10/10 PHP
JavaScript的public、private和privileged模式
2009/12/28 Javascript
基于jquery的一个图片hover的插件
2010/04/24 Javascript
js实现模拟银行卡账号输入显示效果
2015/11/18 Javascript
vue生命周期实例小结
2018/08/15 Javascript
vuejs element table 表格添加行,修改,单独删除行,批量删除行操作
2020/07/18 Javascript
python实现类的静态变量用法实例
2015/05/08 Python
用Python计算三角函数之atan()方法的使用
2015/05/15 Python
使用Python的Twisted框架构建非阻塞下载程序的实例教程
2016/05/25 Python
python搭建虚拟环境的步骤详解
2016/09/27 Python
Python实现压缩文件夹与解压缩zip文件的方法
2018/09/01 Python
详解Python locals()的陷阱
2019/03/26 Python
python实现飞机大战游戏
2020/10/26 Python
python正则表达式的懒惰匹配和贪婪匹配说明
2020/07/13 Python
美国眼镜网站:EyeBuyDirect
2017/04/13 全球购物
现代家居用品及礼品:LBC Modern
2018/06/24 全球购物
芬兰灯具网上商店:Nettilamppu.fi
2018/06/30 全球购物
送给他或她的礼物:FUN.com
2018/08/17 全球购物
乌克兰最大的家用电器和电子产品连锁店:Eldorado
2019/10/02 全球购物
MIRTA官网:手工包,100%意大利制造
2020/02/11 全球购物
Linux文件操作命令都有哪些
2015/02/27 面试题
婚礼新郎父母答谢词
2014/01/16 职场文书
网上蛋糕店创业计划书
2014/01/24 职场文书
无刑事犯罪记录证明范本
2014/09/29 职场文书
学校拾金不昧表扬信
2015/01/16 职场文书
公司财务人员岗位职责
2015/04/14 职场文书
民警忠诚教育心得体会
2016/01/23 职场文书
哪类餐饮行业,最适合在高校创业?
2019/08/19 职场文书
eval(cmd)与eval($cmd)的区别与联系
2021/07/07 PHP
Python装饰器详细介绍
2022/03/25 Python
Go 中的空白标识符下划线
2022/03/25 Golang