Skip to content

Commit e836415

Browse files
committed
递归
1 parent 44518ad commit e836415

File tree

6 files changed

+204
-178
lines changed

6 files changed

+204
-178
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@
55
笔者尝试录制视频教程帮助 Python 初学者掌握常用算法和数据结构,提升开发技能。
66
本教程是付费教程(文字内容和代码免费),因为笔者录制的过程中除了购买软件、手写板等硬件之外,业余需要花费很多时间和精力来录制视频、查资料、编写课件和代码,养家糊口不容易,希望大家体谅。
77

8+
## 链接
9+
10+
[网上阅读《Python 算法与数据结构教程 》](http://ningning.today/python_data_structures_and_algorithms/)
11+
[github 链接](https://github.com/PegasusWang/python_data_structures_and_algorithms)
12+
813
## 痛点
914
- 讲 Python 数据结构和算法的资料很少,中文资料更少
1015
- 很多自学 Python 的工程师对基础不够重视,面试也发现很多数据结构和算法不过关,很多人挂在了基础的数据结构和算法上
1116
- 缺少工程应用场景下的讲解,很多讲算法的资料太『教科书化』。本书实现的代码工程上可用
17+
- 网上很多视频教程比较水,收费还很不合理,纯属智商税
1218

1319
## 作者简介
1420
目前就职于[知乎](https://www.zhihu.com/people/pegasus-wang/activities),从实习期间接触 Python 起一直从事 Python 网站后端开发,有一定 Python 的使用和实践经验。

docs/10_递归/fact.png

12.5 KB
Loading

docs/10_递归/recursion.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# 递归
2+
3+
> Recursion is a process for solving problems by subdividing a larger
4+
> problem into smaller cases of the problem itself and then solving
5+
> the smaller, more trivial parts.
6+
7+
递归是计算机科学里出现非常多的一个概念,有时候用递归解决问题看起来非常简单优雅。
8+
之前讲过的数据结构中我们并没有使用递归,因为递归涉及到调用栈,可能会让初学者搞晕。这一章我们开始介绍递归,
9+
后边讲到树和一些排序算法的时候我们还会碰到它。我非常推荐你先看看《算法图解》第三章 递归,
10+
举的例子比较浅显易懂。
11+
12+
13+
# 什么是递归?
14+
递归用一种通俗的话来说就是自己调用自己,但是需要分解它的参数,让它解决一个更小一点的问题,当问题小到一定规模的时候,需要一个递归出口返回。
15+
这里举一个和其他很多老套的教科书一样喜欢举的例子,阶乘函数,我觉得用来它演示再直观不过。它的定义是这样的:
16+
17+
![](./fact.png)
18+
19+
我们很容易根据它的定义写出这样一个递归函数,因为它本身就是递归定义的。
20+
21+
```py
22+
def fact(n):
23+
if n == 0:
24+
return 1
25+
else:
26+
return n * fact(n-1)
27+
```
28+
看吧,几乎完全是按照定义来写的。我们来看下递归函数的几个特点:
29+
30+
- 递归必须包含一个基本的出口(base case),否则就会无限递归,最终导致栈溢出。比如这里就是 n == 0 返回 1
31+
- 递归必须包含一个可以分解的问题(recursive case)。 要想求得 fact(n),就需要用 n * fact(n-1)
32+
- 递归必须必须要向着递归出口靠近(toward the base case)。 这里每次递归调用都会 n-1,向着递归出口 n == 0 靠近
33+
34+
35+
# 调用栈
36+
看了上一个例子你可能觉得递归好简单,先别着急,我们再举个简单的例子,上边我们并没有讲递归如何工作的。
37+
假如让你输出从 1 到 10 这十个数字,如果你是个正常人的话,我想你的第一反应都是这么写:
38+
39+
```py
40+
def print_num(n):
41+
for i in range(1, n + 1): # 注意很多编程语言使用的都是 从 0 开始的左闭右开区间, python 也不例外
42+
print(i)
43+
44+
45+
if __name__ == '__main__':
46+
print_num(10)
47+
```
48+
49+
我们尝试写一个递归版本,不就是自己调用自己嘛:
50+
51+
```py
52+
def print_num_recursive(n):
53+
if n > 0:
54+
print_num_recursive(n-1)
55+
print(n)
56+
```
57+
58+
你猜下它的输出?然后我们调换下 print 顺序,你再猜下它的输出
59+
60+
```py
61+
def print_num_recursive_revserve(n):
62+
if n > 0:
63+
print(n)
64+
print_num_recursive_revserve(n-1)
65+
```
66+
你能明白是为什么吗?我建议你运行下这几个小例子,它们很简单但是却能说明问题。
67+
计算机内部使用调用栈来实现递归,这里的栈一方面指的是内存中的栈区,一方面栈又是之前讲到的后进先出这种数据结构。
68+
每当进入递归函数的时候,系统都会为当前函数开辟内存保存当前变量值等信息,每个调用栈之间的数据互不影响,新调用的函数
69+
入栈的时候会放在栈顶。视频里我们会画图来演示这个过程。
70+
71+
递归只用大脑不用纸笔模拟的话很容易晕,因为明明是同一个变量名字,但是在不同的调用栈里它是不同的值,所以我建议
72+
你最好手动画画这个过程。
73+
74+
75+
# 用栈模拟递归
76+
刚才说到了调用栈,我们就用栈来模拟一把。之前栈这一章我们讲了如何自己实现栈,不过这里为了不拷贝太多代码,我们直接用 collections.deque 就可以
77+
快速实现一个简单的栈。
78+
79+
```py
80+
from collections import deque
81+
82+
83+
class Stack(object):
84+
def __init__(self):
85+
self._deque = deque()
86+
87+
def push(self, value):
88+
return self._deque.append(value)
89+
90+
def pop(self):
91+
return self._deque.pop()
92+
93+
def is_empty(self):
94+
return len(self._deque) == 0
95+
96+
97+
def print_num_use_stack(n):
98+
s = Stack()
99+
while n > 0: # 不断将参数入栈
100+
s.push(n)
101+
n -= 1
102+
103+
while not s.is_empty(): # 参数弹出
104+
print(s.pop())
105+
```
106+
这里结果也是输出 1 到 10,只不过我们是手动模拟了入栈和出栈的过程,帮助你理解计算机是如何实现递归的,是不是挺简单!现在你能明白为什么上边 print_num_recursive print_num_recursive_revserve 两个函数输出的区别了吗?
107+
108+
# 尾递归
109+
上边的代码示例中实际上包含了两种形式的递归,一种是普通的递归,还有一种叫做尾递归:
110+
111+
```py
112+
def print_num_recursive(n):
113+
if n > 0:
114+
print_num_recursive(n-1)
115+
print(n)
116+
117+
118+
def print_num_recursive_revserve(n):
119+
if n > 0:
120+
print(n)
121+
print_num_recursive_revserve(n-1) # 尾递归
122+
```
123+
124+
概念上它很简单,就是递归调用放在了函数的最后。有什么用呢?
125+
普通的递归, 每一级递归都产生了新的局部变量, 必须创建新的调用栈, 随着递归深度的增加, 创建的栈越来越多, 造成爆栈。虽然尾递归调用也会创建新的栈,
126+
但是我们可以优化使得尾递归的每一级调用共用一个栈!, 如此便可解决爆栈和递归深度限制的问题!
127+
不幸的是 python 默认不支持尾递归优化(见延伸阅读),不过一般尾递归我们可以用一个迭代来优化它。
128+
129+
130+
# 著名的汉诺塔问题
131+
132+
133+
# 延伸阅读
134+
递归是个非常重要的概念,我们后边的数据结构和算法中还会多次碰到它,我建议你多阅读一些资料加深理解:
135+
136+
- 《算法图解》第三章 递归
137+
- 《Data Structures and Algorithms in Python》 第 10 章 Recursion
138+
- [《Python开启尾递归优化!》](https://segmentfault.com/a/1190000007641519)
139+
- [尾调用优化](http://www.ruanyifeng.com/blog/2015/04/tail-call.html)
140+
141+
# 思考题
142+
- 你能举出其他一些使用到递归的例子吗?
143+
- 实现一个 flatten 函数,把嵌套的列表扁平化,你需要用递归函数来实现。比如 [[1,2], [1,2,3] -> [1,2,1,2,3]
144+
- 使用递归和循环各有什么优缺点,你能想到吗?怎么把一个尾递归用迭代替换?
145+
- 递归有时候虽然很优雅,但是时间复杂度却不理想,比如斐波那契数列,它的表达式是 F(n) = F(n-1) + F(n-2),你能计算它的时间复杂度吗?我们怎样去优化它

docs/10_递归/recursion.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# -*- coding: utf-8 -*-
2+
3+
4+
def fact(n):
5+
if n == 0:
6+
return 1
7+
else:
8+
return n * fact(n-1)
9+
10+
11+
def print_num(n):
12+
for i in range(1, n + 1): # 注意很多编程语言使用的都是 从 0 开始的左闭右开区间, python 也不例外
13+
print(i)
14+
15+
16+
def print_num_recursive(n):
17+
if n > 0:
18+
print_num_recursive(n-1)
19+
print(n)
20+
21+
22+
def print_num_recursive_revserve(n):
23+
if n > 0:
24+
print(n)
25+
print_num_recursive_revserve(n-1)
26+
27+
28+
from collections import deque
29+
30+
31+
class Stack(object):
32+
def __init__(self):
33+
self._deque = deque()
34+
35+
def push(self, value):
36+
return self._deque.append(value)
37+
38+
def pop(self):
39+
return self._deque.pop()
40+
41+
def is_empty(self):
42+
return len(self._deque) == 0
43+
44+
45+
def print_num_use_stack(n):
46+
s = Stack()
47+
while n > 0: # 不断将参数入栈
48+
s.push(n)
49+
n -= 1
50+
51+
while not s.is_empty(): # 参数弹出
52+
print(s.pop())

docs/递归/recursion.md

Lines changed: 0 additions & 178 deletions
This file was deleted.

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ pages:
1919
- 哈希表: '7_哈希表/hashtable.md'
2020
- 字典: '8_字典/dict.md'
2121
- 集合: '9_集合/set.md'
22+
- 递归: '10_递归/recursion.md'

0 commit comments

Comments
 (0)