浅谈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中关于时间和日期函数的常用计算总结(time和datatime)
Mar 08 Python
python进阶教程之文本文件的读取和写入
Aug 29 Python
Python的Django框架安装全攻略
Jul 15 Python
实例Python处理XML文件的方法
Aug 31 Python
浅谈numpy数组的几种排序方式
Dec 15 Python
基于python代码实现简易滤除数字的方法
Jul 17 Python
python 处理数字,把大于上限的数字置零实现方法
Jan 28 Python
Python 实现还原已撤回的微信消息
Jun 18 Python
Python高级特性——详解多维数组切片(Slice)
Nov 26 Python
如何表示python中的相对路径
Jul 08 Python
MATLAB 全景图切割及盒图显示的实现步骤
May 14 Python
python控制台打印log输出重复的解决方法
May 14 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
20个PHP常用类库小结
2011/09/11 PHP
PHP中全面阻止SQL注入式攻击分析小结
2012/01/30 PHP
PHP文件注释标记及规范小结
2012/04/01 PHP
php无限遍历文件夹示例分享
2014/03/04 PHP
PHP获取一年中每个星期的开始和结束日期的方法
2015/02/12 PHP
php生成验证码,缩略图及水印图的类分享
2016/04/07 PHP
PHP魔术方法之__call与__callStatic使用方法
2017/07/23 PHP
PHP实现十进制数字与二十六进制字母串相互转换操作示例
2018/08/10 PHP
js将控件隐藏及display属性的使用介绍
2013/12/30 Javascript
按Enter键触发事件的jquery方法实现代码
2014/02/17 Javascript
JavaScript中Textarea滚动条不能拖动的解决方法
2015/12/15 Javascript
原生javascript实现addClass,removeClass,hasClass函数
2016/02/25 Javascript
js css自定义分页效果
2017/02/24 Javascript
微信小程序 slider的简单实例
2017/04/19 Javascript
基于JS对象创建常用方式及原理分析
2017/06/28 Javascript
VueJS 集成 Medium Editor的示例代码 (自定义编辑器按钮)
2017/08/24 Javascript
Vue.js通用应用框架-Nuxt.js的上手教程
2017/12/25 Javascript
浅谈vuex 闲置状态重置方案
2018/01/04 Javascript
vue的安装及element组件的安装方法
2018/03/09 Javascript
Vue 动态组件与 v-once 指令的实现
2019/02/12 Javascript
vue中各种通信传值方式总结
2019/02/14 Javascript
Python里disconnect UDP套接字的方法
2015/04/23 Python
python读取和保存视频文件
2018/04/16 Python
QML使用Python的函数过程解析
2019/09/26 Python
TensorFlow 多元函数的极值实例
2020/02/10 Python
Windows下Pycharm远程连接虚拟机中Centos下的Python环境(图文教程详解)
2020/03/19 Python
解决virtualenv -p python3 venv报错的问题
2021/02/05 Python
Python读写Excel表格的方法
2021/03/02 Python
使用HTML5原生对话框元素并轻松创建模态框组件
2019/03/06 HTML / CSS
比利时香水网上商店:NOTINO
2018/03/28 全球购物
介绍一下ICMP(Internet Control Message Protocol)Internet控制信息协议
2016/11/26 面试题
银行办理业务介绍信
2014/01/18 职场文书
《冬阳童年骆驼队》教学反思
2014/04/15 职场文书
酒店保洁员岗位职责
2015/02/26 职场文书
只需要100行Python代码就可以实现的贪吃蛇小游戏
2021/05/27 Python
pytorch锁死在dataloader(训练时卡死)
2021/05/28 Python