每天学习一点儿算法--递归
递归是很多算法都使用的一种编程方法。听说递归是一种十分优雅的问题解决办法,可是对于初涉递归的我,还没有形成这种独特的体会。
学习使用递归的关键在于:如何将问题分为基线条件和递归条件。
基线条件和递归条件
由于递归函数调用自己,因此编写这样的函数时很容易出错,进而导致无限循环。
例如下面这个函数:
def countdown(i):
"""倒计时"""
print (i)
countdown(i-1)
假设i的初始值为3,运行上述代码后:
3, 2, 1, 0, -1, -2, .........
它会一直运行下去,(可按Ctrl+C停止)
所以,编写递归函数必须要让函数能在某个时候停止递归。
让递归函数停止递归的条件就是基线条件。
递归条件指函数调用自己;基线条件指函数不再调用自己。
现在我们给函数countdown添加基线条件:
def count_down(i):
"""倒计时"""
print(i)
if i <= 1: # 基线条件(指函数不再调用自己)
return
else: # 递归条件(指函数调用自己)
count_down(i-1)
count_down(3)
运行结果:
3
2
1
栈
使用递归必须就要理解一个名为“调用栈”的编程概念。因为递归函数在运行的过程中是存储在栈中的。
栈是一种数据结构,只有两种基本操作:压入(进栈)和弹出(出栈)。且遵循后进先出的规则。
计算机在内部使用的栈被称为调用栈。下面用一个函数来看看计算机如何使用调用栈:
def greet(name):
"""问候1"""
# greet函数问候用户,再调用了另外两个函数
print("hello " + name + " !")
greet2(name)
print("getting reading to say bye...")
bye()
这个函数问候用户,再调用了另外两个函数,这两个函数的代码如下:
def greet2(name):
"""问候2"""
print("how are you, " + name + " ?")
def bye():
"""拜拜"""
print("ok, bye!")
注释:在python中,print也是一个函数,但我们先暂且不考虑它。
假设我们调用greet(“you”)。计算机先为其分配一块内存:
接下来,打印出 hello you !
。再调用函数greet2(“you”)。同样,计算机也为这个函数调用分配一个内存块:
然后打印出 how are you, you ?
。然后从函数调用返回。此时,栈顶的内存块被弹出:
执行完函数greet2后,回到函数greet,并从离开的地方接着往下执行:首先打印 getting ready to say bye...
。再调用函数bye:
然后打印 ok bye !
。并从这个函数返回。
现在又回到了函数greet。由于没有别的事要做,就从函数greet返回。这个被用于存储多个函数变量的栈,称之为调用栈。
递归调用栈的另一个应用就是计算阶乘。下面是一个计算阶乘的递归函数:
def fact(x):
"""计算阶乘的函数"""
if x == 1:
return 1
else:
return x * fact(x-1)
我们来分析一下调用fact(3)时,调用栈是如何变化的:
说明:
- 使用递归不能提高程序的性能,它只是让程序更容易理解。
- 使用栈很方便,但会占据很多的内存
尾递归
最后介绍一个尾递归。尾递归是一种高级递归,它和普通递归函数的区别在于:尾递归在函数执行的最后一步调用自身,而其他递归函数在函数的最后一步不仅调用了自身,还掺杂着其他表达式。
举个例子: 计算阶乘:
def fact(x):
"""计算阶乘的函数"""
if x == 1:
return 1
else:
return x * fact(x-1)
这不是尾递归函数。
def fact(x):
"""尾递归"""
if x == 1:
return 1
else:
return fact(x-1)
这就是尾递归函数。
小结
- 递归指调用自己的函数
- 每个递归函数都有两个条件:基线条件和递归条件
- 栈有两种操作:压入和弹出
- 所有的函数调用都进入调用栈
每天学习一点点,每天进步一点点。
- 洛谷P3379 【模板】最近公共祖先(LCA)(树链剖分)
- 学习使用Jieba1.Jieba2. 特点3.功能4.安装5.使用6.其他中文分词工具
- 如何使用sklearn加载和下载机器学习数据集
- 洛谷P3224 [HNOI2012]永无乡
- 手把手教你使用sklearn快速入门机器学习
- 【 关关的刷题日记48】Leetcode 58. Length of Last Word
- RESTful API 设计指南
- 洛谷P1043 数字游戏
- 使用“空”对象替代引用是否为空判断
- 真是绝了!史上最详细的Jupyter Notebook入门教程
- 10.socket网络编程
- BZOJ1269: [AHOI2006]文本编辑器editor
- 开发人员为何需要企业服务总线?
- 搭建Visual Studio Code+Python开发环境1.对象简介2. 搭建步骤3.小结
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 完美解决pyinstaller打包报错找不到依赖pypiwin32或pywin32-ctypes的错误
- Android开发中Intent.Action各种常见的作用汇总
- Android解决ScrollView下嵌套ListView和GridView中内容显示不全的问题
- Android添加ButterKnife时报错Error:(2, 0) Cannot add extension with name 'android'的解决办法
- Python视频编辑库MoviePy的使用
- python json.dumps中文乱码问题解决
- Android定时器实现定时执行、重复执行、定时重复执行、定次数执行的多种方式
- Android开发导入项目报错Ignoring InnerClasses attribute for an anonymous inner class的解决办法
- Android中buildToolVersion与CompileSdkVersion的区别
- Glide用法与技巧以及优秀库的推荐
- Android整理好的图片压缩工具类
- Android Studio获取网络JSON数据并处理的方法
- Android使用ViewPager快速切换Fragment时卡顿的优化方案
- 在Android打包中区分测试和正式环境浅析
- django 多数据库及分库实现方式