每天学习一点儿算法--递归
递归是很多算法都使用的一种编程方法。听说递归是一种十分优雅的问题解决办法,可是对于初涉递归的我,还没有形成这种独特的体会。
学习使用递归的关键在于:如何将问题分为基线条件和递归条件。
基线条件和递归条件
由于递归函数调用自己,因此编写这样的函数时很容易出错,进而导致无限循环。
例如下面这个函数:
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)
这就是尾递归函数。
小结
- 递归指调用自己的函数
- 每个递归函数都有两个条件:基线条件和递归条件
- 栈有两种操作:压入和弹出
- 所有的函数调用都进入调用栈
每天学习一点点,每天进步一点点。
- 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 数组属性和方法
- SpringMVC配置字符过滤器的两种方式
- SpringMvc使用Json(Java配置方式)
- 使用IDEA创建SpringMvc并整合Thymeleaf(Java配置版)
- Junit报错:Argument(s) are different! Wanted:
- RocketMQ使用过滤器filterServer(4.3版本后)
- Python+Appium运行简单的demo,你需要理解Appium运行原理!
- 网站日志分析完整实践【技术创造101训练营】
- HTML前端知识
- 掌握 AST,轻松落地关键业务「技术创作101训练营」
- 一文轻松学会linux字符设备驱动
- leetcode树之N叉树的前序遍历
- 2020-09-20:如何判断一个数是质数?
- 金九银十Android面试复习题集:关于四大组件中的Activity你了解多少?
- HaseMap的循环姿势你真的懂了吗?
- 历经14天自定义3个注解解决项目的3个Swagger难题