一,认识递归函数
1,什么是递归?
递归的工作原理是,如果函数需要处理的问题大小合适,则直接求解并返回结果,
否则将问题分解成两个或多个更小的子问题,并对子问题进行相同的处理,
直到问题无法分解为止
2,什么是递归函数:
递归函数(recursive function)是指在函数体中可以调用自己的函数
3,语法
def fn():
# ...
if condition:
# 停止自我调用
else:
fn()
# ...
4,递归函数的优点和缺点
递归函数的优点:它们可以帮助程序员在处理复杂问题时提供一种简单且易懂的解决方案。
递归函数使代码具有可读性和可重用性,
而且可以使用递归函数解决使用其他方法难以处理的问题。
递归函数的缺点: 递归函数可能会在运行时占用较多的系统资源,
因为它们需要在堆栈上存储多个函数调用
其次,递归函数可能导致代码变得不容易理解,
因为它具有一定的复杂度
二,应用:
1,使用递归函数计算斐波那契数列
在下面的例子中,输入参数n代表要计算斐波那契数列的第n个项。
函数首先检查n是否等于0或1,如果是,则直接返回n。
否则,函数将n拆分成两个子问题——计算斐波那契数列的第n-1个项和第n-2个项,
并使用自身函数来处理它们。
一旦递归进入终止条件,即n等于0或1时,递归将停止并且函数将返回结果
def fibonacci(n):
if n==0 or n==1:
return n
else:
return fibonacci(n-1)+fibonacci(n-2)
for i in range(20):
print(fibonacci(i),end=" ")
运行结果:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
如果不使用递归解决,
我们看一下用while循环的方式:
# 用while 打印fibonacci的前十个数字
first = 0 # 前两个之一
second = 1 # 前两个之二
i = 1 # 计数器
while i <= 20:
# 得到当前的值,它前面的两个值相加
if i ==1:
current = 0
elif i == 2:
current = 1
else:
current = first + second
print(current, end=" ")
first = second # 得到当前值后,把second赋值给first
second = current # 把current赋值给second
i += 1
运行结果:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
再来一个for循环方式:
first = 0 # 前两个之一
second = 1 # 前两个之二
for i in range(1, 21):
if i == 1:
current = 0
elif i == 2:
current = 1
else:
current = first + second
print(current, end=" ")
first = second # 得到当前值后,把second赋值给first
second = current # 把current赋值给second
运行结果:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
2,使用递归计算阶乘
def factorial(n):
if n == 1: # 正确的返回添加(结束条件)
return 1
else:
return n * factorial(n - 1) # 调用自身函数的行为
num = 5
res = factorial(5)
print(f"{num} 的阶乘是 {res}")
运行结果:
5 的阶乘是 120
不使用递归方式,用while循环解决
num = 5 # 数字
factorial = 1 # 阶乘的最终结果
i = 1 # 计数器
while i <= num:
factorial *= i
i += 1
print(f"{num} 的阶乘是 {factorial}")
运行结果:
5 的阶乘是 120
不使用递归方式,用for循环解决
n = 5 # 要计算阶乘的数字
factorial = 1 # 阶乘的最终结果
for i in range(1, n + 1):
factorial *= i
print(f"{n} 的阶乘是 {factorial}")
运行结果:
5 的阶乘是 120
3,从1一直加到100,求和,
用递归方式
def sum_add(n):
if n > 0:
return n + sum_add(n-1)
return 0
print("从1到100的和为:", sum_add(100))
运行结果:
从1到100的和为: 5050
从1一直加到100,while方式求和
# 计算从1加到100的和
n = 1 # 计数器
sumRes = 0 # 加和的结果
while n <= 100:
sumRes += n
n += 1
print("从1到100的和为:", sumRes)
运行结果:
从1到100的和为: 5050
从1一直加到100,for方式求和
# 计算1加到100的和
sumRes = 0 # 加和的结果
for num in range(1, 101):
sumRes += num
print("从1加到100的和:", sumRes)
运行结果:
从1到100的和为: 5050
4,年龄问题 ,1的年龄不知道,比2大2岁,2比3大2岁,3比4大2岁,4的年龄是40
求1的年龄
# 年龄问题 ,1的年龄不知道,比2大2岁,2比3大2岁,3比4大2岁,4的年龄是40
# 求1的年龄
def age(n):
if n == 4:
return 40
return age(n + 1) + 2
ret = age(1)
print(ret) # 46
运行结果:
46
5,猴子吃桃问题:
猴子第一天摘了若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。
第二天早上又将剩下的桃子吃掉一半,又多吃一个。
以后每天早上都吃前一天的一半零一个。
到第十天的时候再想吃,见只剩下一个桃子了。
求第一天一共摘了多少桃子?
设第九天没吃之前的数量为begin,吃完之后的数量是remain
begin- (begin/2) – 1= remain
可以知道
begin = (remain + 1) * 2
其中remain是当天吃完之后的数量,也是后一天没吃之前的数量
def peach(days):
num = 0
if days == 10:
num = 1
else:
num = (peach(days + 1) + 1) * 2
print("第", days, "天,开始时的数量:",num)
return num
peach(1)
运行结果:
第 10 天,开始时的数量: 1
第 9 天,开始时的数量: 4
第 8 天,开始时的数量: 10
第 7 天,开始时的数量: 22
第 6 天,开始时的数量: 46
第 5 天,开始时的数量: 94
第 4 天,开始时的数量: 190
第 3 天,开始时的数量: 382
第 2 天,开始时的数量: 766
第 1 天,开始时的数量: 1534
用while循环方式解决:
设第九天没吃之前的数量为begin,吃完之后的数量是remain
begin- (begin/2) – 1= remain
可以知道
begin = (remain + 1) * 2
remain = 1 # 第九天吃完后剩余1个
day = 9 # 第几天
while day >= 1:
begin = (remain + 1) * 2 # 得到开始时的数量
print("第", day, "天,开始时的数量:", begin,"吃完后剩余:",remain)
day -= 1 # 回到前一天
remain = begin # 今天开始时的数量,是前一天剩余的数量
运行结果:
第 9 天,开始时的数量: 4 吃完后剩余: 1
第 8 天,开始时的数量: 10 吃完后剩余: 4
第 7 天,开始时的数量: 22 吃完后剩余: 10
第 6 天,开始时的数量: 46 吃完后剩余: 22
第 5 天,开始时的数量: 94 吃完后剩余: 46
第 4 天,开始时的数量: 190 吃完后剩余: 94
第 3 天,开始时的数量: 382 吃完后剩余: 190
第 2 天,开始时的数量: 766 吃完后剩余: 382
第 1 天,开始时的数量: 1534 吃完后剩余: 766
6,把字典扁平化,把形如:
dict1 = {'Tom': {'age': 2, 'city': 'baoding'},
'Jerry': {'age': 3, 'hobby': {'1': 'sing', '2': 'dancing'}}
}
转为
{'Tom.age': 2, 'Tom.city': 'baoding', 'Jerry.age': 3, 'Jerry.hobby.1': 'sing', 'Jerry.hobby.2': 'dancing'}
dict1 = {'Tom': {'age': 2, 'city': 'baoding'},
'Jerry': {'age': 3, 'hobby': {'1': 'sing', '2': 'dancing'}}
}
target = {} # 保存扁平化后的字典
def flatmap(src, prefix=''):
for k, v in src.items():
if isinstance(v, (list, tuple, set, dict)):
flatmap(v, prefix=prefix + k + '.') # 递归调用
else:
target[prefix + k] = v
flatmap(dict1)
print(target)
运行结果:
{'Tom.age': 2, 'Tom.city': 'baoding', 'Jerry.age': 3, 'Jerry.hobby.1': 'sing', 'Jerry.hobby.2': 'dancing'}
7,汉诺塔问题
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
我们编写代码时要遵循汉诺塔的规则: 小圆盘上不能放大圆盘,即大的圆盘不能放在小的圆盘上面
先归纳最简单的情况:
如果A柱上只有一个圆盘,
那么移动圆盘的步骤显然是 从A移动到C,即表示成 A–>C
如果A柱上有两片圆盘,也很容易想到只要3步就可以完成,
即
第1步:A上第一块–>B
第2步:A上第二块–>C
第3步:B上第一块–>C
总结规律即:
将A上面的n-1个圆盘,从A借助C移动到B
将A上最下面的一个圆盘,从A移动到C
将B上面的n-1个圆盘,从B借助A移动到C
我们根据得到的规律,用递归的方式编写代码:
# n: 第几块
# a: 源
# b:辅助
# c:目标
def hanoi(n, a, b, c):
if n == 1:
print("{}:{}->{}".format(1, a, c))
else:
hanoi(n - 1, a, c, b)
print("{}:{}->{}".format(n, a, c))
hanoi(n - 1, b, a, c)
hanoi(2, "A", "B", "C")
运行结果:
1:A->B
2:A->C
1:B->C
如果是3块圆盘:
def hanoi(n, a, b, c):
if n == 1:
print("{}:{}->{}".format(1, a, c))
else:
hanoi(n - 1, a, c, b)
print("{}:{}->{}".format(n, a, c))
hanoi(n - 1, b, a, c)
hanoi(3, "A", "B", "C")
运行结果:
1:A->C
2:A->B
1:C->B
3:A->C
1:B->A
2:B->C
1:A->C
借用玩具店的图片演示一下
8,佩尔数列
佩尔数列是一个整数数列。它的第一项为0,第二项为1,
第3项是第2项的2倍再加上第1项,
第4项是第3项的2倍再加上第2项,……。
佩尔数列的前十个数字是:0 1 2 5 12 29 70 169 408 985
用程序打印出佩尔数列的前二十个数字
def pell(n):
if n == 1:
return 0
elif n == 2:
return 1
elif n > 2:
return pell(n - 1) * 2 + pell(n - 2)
for i in range(1, 20):
print(pell(i), end=" ")
运行结果:
0 1 2 5 12 29 70 169 408 985 2378 5741 13860 33461 80782 195025 470832 1136689 2744210
9,小明读书, 第一天读了全书的一半加二页,
第二天读了剩下的一半加二页,以此类推,
第六天读完了最后的三页,
问全书有多少页?
解析:
第六天未读之前剩了三页
第五天未读之前剩的的页数: (第六天读的页数 +2 ) * 2
def num(n):
if n > 6:
return 0
elif n == 6:
return 3
else:
return (num(n + 1) + 2) * 2
print("全书共:{}页".format(num(1)))
运行结果:
全书共:220页
10,一个人赶着鸭子去每个村庄卖,每经过一个村子卖去所赶鸭子的一半又一只
这样他经过了七个村子后还剩两只鸭子,问他出发时共赶多少只鸭子?
解析:
经过第七个村子后还剩2只鸭子
经过第六个村子后还剩( 2 + 1) * 2
需要注意的,问题问的是出发时共赶了多少只鸭子?
不是经过第一个村子后的鸭子数量,
所以求数量时应该调用:duck(0)
def duck(n):
if n == 7:
return 2
else:
tmp = (duck(n+1)+1)*2
return tmp
print(duck(1))
print(duck(0))
运行结果:
254
510
11,分鱼问题:
A,B,C,D,E 5人合伙夜间捕鱼,上岸时都疲惫不堪,各自在湖边的树丛中找地方就睡觉了。
清晨,第一个人醒来,将🐟分成5份,把多余的一条扔回湖中,拿自己的一份回家了;
第二个人醒来,也将🐟分成5份,扔掉多余的一条🐟,拿自己的一份回家了;
接着,其余3人依次醒来,也都按同样的办法分🐟。
问:5人至少共捕到多少条鱼?每人醒来后看到多少条鱼?
解析:
fish[0]为5个人合伙捕鱼的总数,也是A看到的条数
fish[1]=((fish[0]-1)/5)*4,也是B看到的条数
fish[2]=((fish[1]-1)/5)*4,也是C看到的条数
fish[3]=((fish[2]-1)/5)*4,也是D看到的条数
fish[4]=((fish[3]-1)/5)*4,也是E看到的条数
要注意的地方:
每个人看到的鱼的数量 – 1 之后,
可以分成平均分成5份,即:可以被5整除,
这是分鱼成立的条件
假设第5个人E给自己分配了i条
则它看到的数量是i*5+1条,
则需要从第5个人一直逆推到第一个人,
如果每一个人看到的数量-1后都可以平均分成5份且是整数,
则表示数字正确
因为不确定第五个人E给自己分配的数量是多少,
我们用while循环方式,从1开始试探,
看哪个数字符合条件
def fish(n, z):
if (z - 1) % 5 == 0:
if n == 1:
return 1
else:
return fish(n - 1, (z - 1) / 5 * 4)
else:
return 0
i = 0
while True:
i += 1
x = i * 5 + 1
# print(i, x)
if fish(5, x):
print(x)
break
运行结果:
3121
12,求两个数的最小公倍数。
思路:如果a能被b整除,则表示a就是最小公倍数
如果a不能被b整除,则让a每次自增一个a,
直到能被b整除,即得到最小公倍数
def lcm(s, m, n):
if s % n == 0:
return s
else:
return lcm(s + m, m, n)
a = 3
b = 4
l = lcm(a, a, b)
print("最小公倍数:", l)
运行结果:
最小公倍数: 12
13,求两个数的最大公约数
欧几里得算法是用来求两个正整数最大公约数的算法。
古希腊数学家欧几里得在其著作《The Elements》中最早描述了这种算法,
所以被命名为欧几里得算法
以除数和余数反复做除法运算,当余数为 0 时,取当前算式中的除数为最大公约数
def gcd(m, n):
temp = m % n
# print(m, n, temp)
if temp == 0:
return n
else:
m = n
n = temp
return gcd(m, n)
a = 45
b = 60
g = gcd(a, b)
print("最大公约数:", g)
运行结果:
最大公约数: 15
14,角谷定理:
输入一个自然数,若为偶数,则把它除以2,若为奇数,则把它乘以3加1
经过如此有限次运算后,总可以得到自然数值1
求经过多少次可得到自然数1
times = 0
def fun(num):
global times
if num == 1:
print(num)
print("Step", times)
return
else:
print(num)
if num % 2 == 0:
times += 1
num /= 2
else:
times += 1
num = num * 3 + 1
fun(num)
fun(22)
print(times)
运行结果:
22
11.0
34.0
17.0
52.0
26.0
13.0
40.0
20.0
10.0
5.0
16.0
8.0
4.0
2.0
1.0
Step 15
15
15,把十进制转换为二进制
十进制转二进制的做法就是除2取余
_a = [] # 保存得到余数的列表
def to2(n):
global _a
if n == 1:
print(n % 2)
_a.append(n % 2)
return
else:
print(n % 2)
_a.append(n % 2)
to2(n // 2)
num = 16
to2(num)
_a.reverse()
print(_a)
运行结果:
0
0
0
0
1
[1, 0, 0, 0, 0]
三,递归函数调用深度的默认最大值为 1000
1,由于递归会产生多次函数调用,而函数调用会消耗代码的栈空间,
如果递归的深度太大,会导致栈溢出
所以并不是可以无限深度调用,
如何查看递归函数调用的最大深度值?
import sys
# 得到最大调用深度
print(sys.getrecursionlimit()) # 1000
运行结果:
1000
2、当调用阶乘使用超过最大深度值时,会发生RecursionError异常
def sum_add(n):
if n > 0:
return n + sum_add(n - 1)
return 0
print("从1到100的和为:", sum_add(1000))
运行结果:
Traceback (most recent call last):
File "/Users/liuhongdi/python_work/tutorial/demo1/function/recursive.py", line 108, in <module>
print("从1到100的和为:", sum_add(1000))
^^^^^^^^^^^^^
File "/Users/liuhongdi/python_work/tutorial/demo1/function/recursive.py", line 104, in sum_add
return n + sum_add(n - 1)
^^^^^^^^^^^^^^
File "/Users/liuhongdi/python_work/tutorial/demo1/function/recursive.py", line 104, in sum_add
return n + sum_add(n - 1)
^^^^^^^^^^^^^^
File "/Users/liuhongdi/python_work/tutorial/demo1/function/recursive.py", line 104, in sum_add
return n + sum_add(n - 1)
^^^^^^^^^^^^^^
[Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded
3,如果想修改递归最大调用深度的限制值,用sys.setrecursionlimit()方法,sys.setrecursionlimit()
方法:
功能:用于将Python解释器递归的最大深度设置为指定的限制值。
此限制可防止任何程序进入无限递归,
否则无限递归将导致堆栈溢出并使Python崩溃。
需要注意的是修改要很小心,因为太高限制可能会导致崩溃
语法:
sys.setrecursionlimit(limit)
参数:limit:它是整数类型的值,表示python解释器递归堆栈的深度限制
返回:此方法不返回任何内容
import sys
# 设定最大调用深度
sys.setrecursionlimit(1002)
def sum_add(n):
if n > 0:
return n + sum_add(n - 1)
return 0
print("从1到100的和为:", sum_add(1000)) # 500500
运行结果:
从1到100的和为: 500500