1. 递归概述

递归( recursion)是一种编程技巧,某些情况下,甚至是无可替代的技巧。递归可以大幅简化代码,看起来非常简洁,但递归设计却非常抽象,不容易掌握。通常,我们都是自上而下的思考问题, 递归则是自下而上的解决问题――这就是递归看起来不够直观的原因。那么,究竟什么是递归呢?让我们先从生活中找一个栗子。

我们都有在黑暗的放映厅里找座位的经验:问问前排的朋友坐的是第几排,加上一,就是自己当前所处位置的排号。如果前排的朋友不知道自己是第几排,他可以用同样的方法得到自己的排号,然后再告诉你。如果前排的前排的朋友也不知道自己是第几排,他就如法炮制。这样的推导,不会无限制地进行下去,因为问到第一排的时候,坐在第一排的朋友一定会直接给出答案的。这就是递归算法在生活中的应用实例。

关于递归,不太严谨的定义是"一个函数在运行时直接或间接地调用了自身”。严谨一点的话,一个递归函数必须满足下面两个条件:

至少有一个明确的递归结束条件,我们称之为递归出口,也有人喜欢把该条件叫做递归基。

有向递归出口方向靠近的直接或间接的自身调用(也被称作递归调用)。

递归虽然晦涩,亦有规律可循。掌握了基本的递归理论,才有可能将其应用于复杂的算法设计中。

2. 线性递归

我们先从最经典的两个递归算法开始――阶乘(factorial)和斐波那契数列(Fibonacci sequence)。几乎所有讨论递归算法的话题,都是从从它们开始的。阶乘的概念比较简单,唯一需要说明的是,0的阶乘是1而非0。为此,我专门请教了我的女儿,她是数学专业的学生。斐波那契数列,又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列是这样定义的:

F(0)=1,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N,N为正整数集)

阶乘和斐波那契数列的递归算法如下:

def factorial(n):

if n == 0: # 递归出口

return 1

return n*factorial(n-1) # 向递归出口方向靠近的自身调用

def fibonacci(n):

if n < 2: # 递归出口

return 1

return fibonacci(n-1) + fibonacci(n-2) # 向递归出口方向靠近的自身调用

这两个函数的结构都非常简单,递归出口和自身调用清晰明了,但二者有一个显著的区别:阶乘函数中,只用一次自身调用,而斐波那契函数则有两次自身调用。

阶乘递归函数每一层的递归对自身的调用只有一次,因此每一层次上至多只有一个实例,且它们构成一个线性的次序关系。此类递归模式称作"线性递归”,这是递归最基本形式。非线性递归(比如斐波那契递归函数)在每一层上都会产生两个实例,时间复杂度为

,极易导致堆栈溢出。

其实,用循环的方法同样可以简洁地写出上面两个函数。的确,很多情况下,递归能够解决的问题,循环也可以做到。但是,更多的情况下,循环是无法取代递归的。因此,深入研究递归理论是非常有必要的。

3. 尾递归

接下来,我们将上面的阶乘递归函数改造一下,仍然用递归的方式实现。为了便于比较,我们把两种算法放在一起。

def factorial_A(n):

if n == 0: # 递归出口

return 1

return n*factorial_A(n-1) # 向递归出口方向靠近的自身调用

def factorial_B(n, k=1):

if n == 0: # 递归出口

return k

k *= n

n -= 1

return factorial_B(n,k) # 向递归出口方向靠近的自身调用

比较 factorial_A() 和 factorial_B() 的写法,就会发现很有意思的问题。factorial_A() 的自身调用属于表达式的一部分,这意味着自身调用不是函数的最后一步,而是拿到自身调用的结果后,需要再做一次乘法运算;factorial_B() 的自身调用则是函数的最后一步。像 factorial_B() 函数这样,当自身调用是整个函数体中最后执行的语句,且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归(Tail Recursion)。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。

分别使用 factorial_A() 和 factorial_B() 计算5的阶乘,下图所示的计算过程,清晰展示了尾递归的优势:不用花费大量的栈空间来保存上次递归中的参数、局部变量等,这是因为上次递归操作结束后,已经将之前的数据计算出来,传递给当前的递归函数,这样上次递归中的局部变量和参数等就会被删除,释放空间,从而不会造成栈溢出。

factorial_A(5)

5 * factorial_A(4)

5 * 4 * factorial_A(3)

5 * 4 * 3 * factorial_A(2)

5 * 4 * 3 * 2 * factorial_A(1)

5 * 4 * 3 * 2 * 1 * factorial_A(0)

5 * 4 * 3 * 2 * 1

5 * 4 * 3 * 2

5 * 4 * 6

5 * 24

120

factorial_B(5, k=1)

factorial_B(4, k=5)

factorial_B(3, k=20)

factorial_B(2, k=60)

factorial_B(1, k=120)

factorial_B(0, k=120)

120

尾递归虽然有低耗高效的优势,但这一类递归一般都可转化为循环语句。

4. 单向递归

前文中两个递归函数,不管是阶乘还是斐波那契数列,递归总是向着递归出口方向进行,没有分支,没有反复,这样的递归,我们称之为单向递归。在文件递归遍历等应用场合,搜索完一个文件夹,通常要返回至父级目录,继续搜索其他兄弟文件夹,这个过程就不是单向的,而是有分叉的、带回溯的。通常复杂递归都不是单向的,算法设计起来就比较困难。

import os

def ergodic(folder):

for root, dirs, files in os.walk(folder):

for dir_name in dirs:

print(os.path.join(root, dir_name))

for file_name in files:

print(os.path.join(root, file_name))

上面是借助于 os 模块的 walk() 实现的基于循环的文件遍历方法。虽然是循环结构,如果不熟悉 walk() 的话,这个函数看起来还是很不直观。我更喜欢下面的递归遍历方法。

import os

def ergodic(folder):

for item in os.listdir(folder):

obj = os.path.join(folder, item)

print(obj)

if os.path.isdir(obj):

ergodic(obj)

5. 深度优先与广度优先

遍历文件通常有两种策略:深度优先搜索 DFS(depth-first search) 和广度优先搜索BFS(breadth-first search) 。顾名思义,深度优先就是优先处理本级文件夹中的子文件夹,递归向纵深发展;广度优先就是优先处理本级文件夹中的文件,递归向水平方向发展。

import os

def ergodic_DFS(folder):

"""基于深度优先的文件遍历"""

dirs, files = list(), list()

for item in os.listdir(folder):

if os.path.isdir(os.path.join(folder, item)):

dirs.append(item)

else:

files.append(item)

for dir_name in dirs:

ergodic(os.path.join(folder, dir_name))

for file_name in files

print(os.path.join(folder, file_name))

def ergodic_BFS(folder):

"""基于广度优先的文件遍历"""

dirs, files = list(), list()

for item in os.listdir(folder):

if os.path.isdir(os.path.join(folder, item)):

dirs.append(item)

else:

files.append(item)

for file_name in files

print(os.path.join(folder, file_name))

for dir_name in dirs:

ergodic(os.path.join(folder, dir_name))

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

递归算法经典实例python-浅谈Python 递归算法指归相关推荐

  1. Python - 浅谈Python的编译与反编译

    1 - Python编译过程涉及的文件 py 源代码文件,由python.exe解释,可在控制台下运行,可用文本编辑器进行编辑: pyc 源代码文件经过编译后生成的二进制文件,无法用文本编辑器进行编辑 ...

  2. 浅谈Python爬虫(五)【网易云热评爬取实例】

    浅谈Python爬虫(五) 目的:爬取网易云歌单所有歌曲的信息及热评 Python环境:3.7 编译器:PyCharm2019.1.3专业版 存储格式:JSON 1.分析网页 进入网易云音乐首页,点击 ...

  3. python新式类和旧式类的区别_浅谈python新式类和旧式类区别

    python的新式类是2.2版本引进来的,我们可以将之前的类叫做经典类或者旧式类. 为什么要在2.2中引进new style class呢?官方给的解释是: 为了统一类(class)和类型(type) ...

  4. python类中方法的执行顺序-浅谈Python的方法解析顺序(MRO)

    方法解析顺序, Method Resolution Order 从一段代码开始 考虑下面的情况: class A(object): def foo(self): print('A.foo()') cl ...

  5. python中 是什么类型_浅谈python中的变量默认是什么类型

    浅谈python中的变量默认是什么类型 1.type(变量名),输出的结果就是变量的类型: 例如 >>> type(6) 2.在Python里面变量在声明时,不需要指定变量的类型,变 ...

  6. python中判断列表数据类型_浅谈Python数据类型判断及列表脚本操作

    数据类型判断 在python(版本3.0以上)使用变量,并进行值比较时.有时候会出现以下错误: TypeError: unorderable types: NoneType() < int() ...

  7. 浅谈Python http库 httplib2

    为什么80%的码农都做不了架构师?>>>    浅谈Python http库 httplib2 http.client 是实现了rfc 2616, http 协议的底层库 urlli ...

  8. python 字典键值重复_浅谈python字典多键值及重复键值的使用

    在python中使用字典,格式如下: dict={ key1:value1 , key2;value2 ...} 在实际访问字典值时的使用格式如下: dict[key] 多键值 字典的多键值形式如下: ...

  9. python竞赛_浅谈Python在信息学竞赛中的运用及Python的基本用法

    浅谈Python在信息学竞赛中的运用及Python的基本用法 前言 众所周知,Python是一种非常实用的语言.但是由于其运算时的低效和解释型编译,在信息学竞赛中并不用于完成算法程序.但正如LRJ在& ...

  10. 浅谈 Python 程序和 C 程序的整合

    浅谈 Python 程序和 C 程序的整合 Python 是一种用于快速开发软件的编程语言,它的语法比较简单,易于掌握,但存在执行速度慢的问题,并且在处理某些问题时存在不足,如对计算机硬件系统的访问, ...

最新文章

  1. socket层内容详解二
  2. developer console 学习
  3. LeetCode 40 组合总和 II
  4. 汽车软件开发的8种颠覆性趋势及国产自主可控的多领域全数字实时仿真平台SkyEye
  5. ArcSDE的版本管理机制
  6. Atitit.自然语言处理--摘要算法---圣经章节旧约39卷概览bible overview v2 qa1.docx
  7. 使用vue+HBulider云游戏接口开发单击斗地主网页版
  8. hibernate二级缓存(二)二级缓存实现原理简单剖析
  9. Cocos2d-x中的BMFont(Fnt)字体改名
  10. git小乌龟版本回退
  11. 主机信息采集(域名、网站备案等)初学者指南
  12. 周立功烧写器(ZLG SMARTPRO5000U-Plus)烧写验证
  13. vs项目中的筛选器(filter)
  14. 优麒麟搜狗输入法简繁切换
  15. 《动手学深度学习》(PyTorch版)代码注释 - 51 【Style_transfer】
  16. opencv-6-图像绘制与line 函数剖析
  17. 史上最难英语单词——as的七十二变【猴精】
  18. 博客中国2004中文非主流网站100强
  19. linux待机唤醒_Linux电源管理-休眠与唤醒
  20. 2021-07-30嵌入式学习---智能家居项目

热门文章

  1. 简单说下H5+打包apk
  2. 程序下载至开发板 芯片超时无应答,无法连接
  3. 【速度↑20%模型尺寸↓36%】极简开源人脸检测算法升级
  4. Ansys 2022R2安装教程
  5. IAR报错:新手刚使用K60(非UD)时容易碰到的问题:芯片被锁,missing FlashK60XX.board
  6. oracle 数据库er生成,oracle数据库生成er图
  7. 分享一个蓝屏代码查询器
  8. 计算机信息技术身边的具体应用案例,2016信息技术案例分析.doc
  9. head first 设计模式源码
  10. 2.vue的不更新特性-重用机制和key属性-data及其他字段-vue生命周期