递归

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

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

递归的分类

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

线性递归

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

例如:

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理解递归的方法总结相关推荐

  1. python递归详解_python基于递归解决背包问题详解

    递归是个好东西,任何具有递归性质的问题通过函数递归调用会变得很简单.一个很复杂的问题,几行代码就能搞定. 最简单的递归问题:现有重量为weight的包,有若干重量分别为W1,W2.....Wn的物品, ...

  2. python录音详解_python音频处理的示例详解

    准备工作: 首先,我们需要 import 几个工具包,一个是 python 标准库中的 wave 模块,用于音频处理操作,另外两个是 numpy 和 matplot,提供数据处理函数. 一:读取本地音 ...

  3. python数据库环境详解_python中MySQL数据库相关操作

    一 安装基本环境 1 简介 MySQL 基于TCP 协议之上的开发,但是网络连接后,传输的数据必须遵循MySQL的协议,封装好MySQL协议的包,就是驱动程序 MySQL 的驱动 MySQLDB 最有 ...

  4. python excel详解_Python - excel 详解

    Python读excel,2003用xlrd,2007和2010用openpyxl xlrd介绍:http://pypi.python.org/pypi/xlrd 转自:http://huaxia52 ...

  5. python递归详解_打破递归栈的深度限制: 解析一种Python尾递归优化的方法

    Python的递归调用栈的深度有限制, 可以通过sys.getrecursionlimit()查看. 尾递归在很多语言中都可以被编译器优化, 基本都是直接复用旧的执行栈, 不用再创建新的栈帧, 原理上 ...

  6. python timer详解_python线程定时器Timer实现原理解析

    这篇文章主要介绍了python线程定时器Timer实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.线程定时器Timer原理 原理比较 ...

  7. python学习详解_Python学习入门到精通:Python列表讲解

    列表导读 从列表开始,我们将陆续学习元组,集合,字典这4中容器类型数据.容器类型数据,只是望文生义,你大概能够猜测出这4种数据类型的作用,他们如同容器一样,可以存储int,float,bool,str ...

  8. python关键字详解_Python 中的关键字with详解

    在 Python 2.5 中,with关键字被加入.它将常用的 try ... except ... finally ...模式很方便的被复用.看一个最经典的例子: with open('file.t ...

  9. python变量详解_python基础教程-03-变量详解

    变量就像一个小罐子,里面是存放着各种数据类型的数据,并且在程序运行过程中会发生变化.变量名在一个工作空间内是唯一的,通过变量的名字就能找到对应的数据. 变量的赋值 变量的赋值就可以理解为往小罐子里存放 ...

最新文章

  1. git前端工程实现ci_大前端项目代码重用,也许lerna是最好的选择
  2. 动态规划——矩阵中的最短路径长度
  3. 如何使用 Pylint 来规范 Python 代码风格
  4. linux系统中-E,-S,-c的区别和作用(怎么讲代码转化为机器识别的语言)
  5. 阿里云使用js 实现OSS图片上传、获取OSS图片列表、获取图片外网访问地址(读写权限私有、读写权限公共);...
  6. python:from skimage.measure import find_contours
  7. Java_GUI创建单机版QQ聊天小程序并实现简单的小功能(附所有源码)
  8. 程序员入职 6 天即被开除:项目丢了,新人背锅?
  9. PADS9.5 原理图和封装制作——以STM32F103为例
  10. 【Visual C++】游戏开发笔记二十六 DirectX 11各组件的介绍第一个DirectX 11 Demo的创建
  11. 【Android】ListView 控件的简单使用
  12. 用java类和对象写一个简单的回合制对战游戏
  13. Adobe Photoshop CC 2019( adobe ps cc)果然不一般
  14. 一个程序员未来5年的自我规划
  15. unexpected indent解决方法
  16. C语言-条件与循环-学习笔记05
  17. 正高职称相当于公务员的什么级别?为什么有人说评上正高就值了
  18. document.documentElement与document.body
  19. Linux环境释放内存
  20. 201671010412 郭佳 实验二 软件工程个人项目

热门文章

  1. 优化ASP.NET应用程序性能研究与探讨
  2. sql server 2005 express附加数据库出错解决方法——添加数据库用户
  3. 大家都较熟悉之 Kubernetes API 分析
  4. 视觉直观感受7种常用的排序算法
  5. UITextField详解
  6. [C#]手把手教你打造Socket的TCP通讯连接(一)
  7. windows XP cmd命令集
  8. CLR自定义菜单项(ToolStripItem)
  9. NHibernate简介
  10. java 图形库_OpenGL开放图形java库jogamp-all-platforms.7z