浅谈Python 递归算法指归


Posted in Python onAugust 22, 2019

1. 递归概述

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

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

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

  1. 至少有一个明确的递归结束条件,我们称之为递归出口,也有人喜欢把该条件叫做递归基。
  2. 有向递归出口方向靠近的直接或间接的自身调用(也被称作递归调用)。

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

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) # 向递归出口方向靠近的自身调用

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

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

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

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中的split()函数的使用方法
Apr 07 Python
PyMongo安装使用笔记
Apr 27 Python
使用Python写一个贪吃蛇游戏实例代码
Aug 21 Python
Python使用SQLite和Excel操作进行数据分析
Jan 20 Python
python3+PyQt5图形项的自定义和交互 python3实现page Designer应用程序
Jul 20 Python
Python使用Selenium爬取淘宝异步加载的数据方法
Dec 17 Python
python实现按行分割文件
Jul 22 Python
python 动态调用函数实例解析
Oct 21 Python
Django 自动生成api接口文档教程
Nov 19 Python
基于python实现可视化生成二维码工具
Jul 08 Python
深入浅析pycharm中 Make available to all projects的含义
Sep 15 Python
PyQt QMainWindow的使用示例
Mar 24 Python
python求加权平均值的实例(附纯python写法)
Aug 22 #Python
python求平均数、方差、中位数的例子
Aug 22 #Python
python2和python3实现在图片上加汉字的方法
Aug 22 #Python
Python使用微信itchat接口实现查看自己微信的信息功能详解
Aug 22 #Python
简单了解python 生成器 列表推导式 生成器表达式
Aug 22 #Python
Python实现的微信红包提醒功能示例
Aug 22 #Python
Python PIL图片添加字体的例子
Aug 22 #Python
You might like
php下使用curl模拟用户登陆的代码
2010/09/10 PHP
美图秀秀web开放平台--PHP流式上传和表单上传示例分享
2014/06/22 PHP
PHP生成及获取JSON文件的方法
2016/08/23 PHP
php 类中的常量、静态属性、非静态属性的区别
2017/04/09 PHP
tp5递归 无限级分类详解
2019/10/18 PHP
在JavaScript中使用inline函数的问题
2007/03/08 Javascript
JavaScript 数组运用实现代码
2010/04/13 Javascript
jquery及原生js获取select下拉框选中的值示例
2013/10/25 Javascript
js字符串日期yyyy-MM-dd转化为date示例代码
2014/03/06 Javascript
JavaScript之数组(Array)详解
2015/04/01 Javascript
vue.js树形组件之删除双击增加分支实例代码
2017/02/28 Javascript
Vue.js父与子组件之间传参示例
2017/02/28 Javascript
Vue实现textarea固定输入行数与添加下划线样式的思路详解
2018/06/28 Javascript
原生js封装的ajax方法示例
2018/08/02 Javascript
Vue表单及表单绑定方法
2018/09/04 Javascript
JavaScript设计模式之责任链模式实例分析
2019/01/16 Javascript
vue 遮罩层阻止默认滚动事件操作
2020/07/28 Javascript
vue 解决无法对未定义的值,空值或基元值设置反应属性报错问题
2020/07/31 Javascript
[02:16]深扒TI7聊天轮盘语音出处2
2017/05/11 DOTA
Python中使用ElementTree解析XML示例
2015/06/02 Python
使用Python的Scrapy框架十分钟爬取美女图
2016/12/26 Python
Python实现判断一个字符串是否包含子串的方法总结
2017/11/21 Python
numpy.random.shuffle打乱顺序函数的实现
2019/09/10 Python
numpy按列连接两个维数不同的数组方式
2019/12/06 Python
python3 sorted 如何实现自定义排序标准
2020/03/12 Python
python图片验证码识别最新模块muggle_ocr的示例代码
2020/07/03 Python
ECCO俄罗斯官网:北欧丹麦鞋履及皮具品牌
2020/06/26 全球购物
介绍一下SQL注入攻击的种类和防范手段
2012/02/18 面试题
《理想》教学反思
2014/02/17 职场文书
美丽家庭事迹材料
2014/05/03 职场文书
领导干部廉政自律承诺书
2014/05/26 职场文书
党性锻炼的心得体会
2014/09/03 职场文书
初三英语教学计划
2015/01/23 职场文书
雾霾停课通知
2015/04/24 职场文书
《葡萄沟》教学反思
2016/02/23 职场文书
python获取带有返回值的多线程
2022/05/02 Python