Python入门篇-递归函数(recursion)
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.递归概述
(1)函数直接或者间接调用自身就是递归;(2)递归需要有边界,递归前进段,递归返回段;(3)递归一定要有边界条件;(4)当边界条件不满足的时候,递归前进; (5)当边界条件满足的时候,递归返回;
二.递归案例
1 #!/usr/bin/env python
2 #_*_coding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
5 #EMAIL:y1053419035@qq.com
6
7 importsys8
9 print("默认的递归调用次数限制0是:{} 次".format(sys.getrecursionlimit()))10
11 sys.setrecursionlimit(20000) #我们修改默认的递归调用次数
12
13 print("当前递归调用次数限制是:{} 次".format(sys.getrecursionlimit()))14 """
15 递归要求:16 递归一定要有退出条件,递归调用一定要执行到这个退出条件。没有退出条件的递归调用,就是无限调用。17 递归调用的深度不宜过深:18 Python对递归调用的深度做了限制,以保护解释器19 超过递归深度限制,抛出:"RecursionError: maxinum recursion depth exceeded" 即超过了最大递归深度20 sys.getrecursionlimit()21 """
22
23
24
25 """
26 斐波拉契数字推理公式:F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)27 """
28 deffib(n):29 return 1 if n < 2 else fib(n-1) + fib(n-2)30
31 for i in range(20):32 print(fib(i), end=' ')33
34
35 #以上代码执行结果如下:
36 默认的递归调用次数限制0是:1000次37 当前递归调用次数限制是:20000次38 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
三.递归的性能
1>.使用for循环打印斐波拉契前35个数字
1 #!/usr/bin/env python
2 #_*_coding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
5 #EMAIL:y1053419035@qq.com
6
7
8 importdatetime9
10 start =datetime.datetime.now()11
12 pre =013
14 cur = 1 #No1
15
16 print(pre, cur, end=' ')17
18 n = 35
19
20 for i in range(n-1):21 pre, cur = cur, pre +cur22 print(cur, end=' ')23
24 delta = (datetime.datetime.now() -start).total_seconds()25
26 print("\n递归调用打印斐波拉契钱35个数字的时间为:{}".format(delta))27
28
29
30 #以上代码直接结果如下:
31 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465
32 递归调用打印斐波拉契钱35个数字的时间为:0.0
2>.使用递归方式打印斐波拉契前35个数字
1 #!/usr/bin/env python
2 #_*_coding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
5 #EMAIL:y1053419035@qq.com
6
7 importdatetime8
9 n = 35
10
11 start =datetime.datetime.now()12
13 deffib(n):14 return 1 if n < 2 else fib(n-1) + fib(n-2)15
16 for i inrange(n):17 print(fib(i), end=' ')18
19 delta = (datetime.datetime.now() -start).total_seconds()20
21 print("\n递归调用打印斐波拉契钱35个数字的时间为:{}".format(delta))22
23
24
25 #以上代码直接结果如下:
26 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465
27 递归调用打印斐波拉契钱35个数字的时间为:5.93134
3>.递归优化方案
循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
fib函数代码极简易懂,但是只能获取到最外层的函数调用,内部递归都是中间结果。而且给定一个n都要进行2n次递归,深度月神,效率月底。为了获取斐波那契数列需要外面在套一个n次的循环,效率就更低了。
递归还有深度限制,如果递归复杂,函数反复压栈,栈内存很快就溢出了
思考:这个极简的递归代码能否提高性能呢?下面是优化后的代码,效率有明显的提示,代码如下所示:
#!/usr/bin/env python#_*_coding:utf-8_*_#@author :yinzhengjie#blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/#EMAIL:y1053419035@qq.com
importdatetime
start=datetime.datetime.now()
pre=0
cur= 1 #No1
print(pre, cur, end=' ')"""改进后的fib函数和循环的思想类似
参数n是边界条件,用n来计算
上一次的结果直接作为参数的实参
效率很高
和循环比较,性能相近。所以并不是说递归一定效率低下。但是递归有深度限制。"""
def fib(n, pre=0,cur=1): #recursion
pre, cur = cur, pre +curprint(cur, end=' ')if n == 2:returnfib(n-1, pre, cur)
fib(35)
delta= (datetime.datetime.now() -start).total_seconds()print("\n递归调用打印斐波拉契钱35个数字的时间为:{}".format(delta))#以上代码直接结果如下:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465递归调用打印斐波拉契钱35个数字的时间为:0.0
4>.间接递归
#!/usr/bin/env python#_*_coding:utf-8_*_#@author :yinzhengjie#blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/#EMAIL:y1053419035@qq.com
deffoo():
bar()defbar():
foo()"""我们这里看起来只是调用foo函数,但是进入foo函数中的函数体中我们发现调用bar函数,进入bar函数中的函数体中我们发现它
又再次调用foo函数。因此我们执行该代码后会发现跑出来异常:“RecursionError: maximum recursion depth exceeded”.
这就形成了间接递归,是通过别的函数调用了函数自身。但是,如果构成了循环递归调用是非常危险的,但是往往这种情况在代码
复杂的情况下,还是可能发生这种调用。要用代码的规范来避免这种递归调用的发生。"""foo()
5>.递归总结
(1)递归是一种很自然的表达,符合逻辑思维
(2)递归相对运行效率低,每一次调用函数都要开辟栈帧
(3)递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了
(4)如果有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
(5)绝大多数递归,都可以使用循环实现
(6)即使递归代码很简洁,但是能不用则不用递归
四.递归练习
1>.求N的阶乘
#!/usr/bin/env python#_*_coding:utf-8_*_#@author :yinzhengjie#blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/#EMAIL:y1053419035@qq.com
deffac(n):if n == 1:return 1
return n * fac(n-1)
N= 5
print("{} 的阶乘为:{}".format(N,fac(N)))#以上代码执行结果如下:
5 的阶乘为:120
解法一(推荐使用这种方法,该函数性能最佳,因为时间复杂度是相同的,该解法最简单)
#!/usr/bin/env python#_*_coding:utf-8_*_#@author :yinzhengjie#blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/#EMAIL:y1053419035@qq.com
def fac(n,p = 1):if n == 1:returnp
p*=n#print(p)
return fac(n - 1,p)
N= 5
print("{} 的阶乘为:{}".format(N,fac(N)))#以上代码执行结果如下:
5 的阶乘为:120
解法二
#!/usr/bin/env python#_*_coding:utf-8_*_#@author :yinzhengjie#blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/#EMAIL:y1053419035@qq.com
def fac(n,p =None):if p isNone:
p= [1]if n == 1:returnp[0]
p[0]*=nreturn fac(n - 1,p)
N= 5
print("{} 的阶乘为:{}".format(N,fac(N)))#以上代码执行结果如下:
5 的阶乘为:120
解法三
2>.将一个数逆序放入列表中,例如:1234=>【4,3,2,1】
#!/usr/bin/env python#_*_coding:utf-8_*_#@author :yinzhengjie#blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/#EMAIL:y1053419035@qq.com
data= str(1234)defrevert(x):if x == -1:return ""
return data[x] + revert(x -1)print(revert(len(data) - 1))#以上代码执行结果如下:
4321
解法一
#!/usr/bin/env python#_*_coding:utf-8_*_#@author :yinzhengjie#blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/#EMAIL:y1053419035@qq.com
def revert(n,list_1 =None):if list_1 isNone:
list_1=[]
x,y= divmod(n,10)
list_1.append(y)if x ==0:returnlist_1returnrevert(x,list_1)print(revert(12345))#以上代码执行结果如下:
[5, 4, 3, 2, 1]
解法二
#!/usr/bin/env python#_*_coding:utf-8_*_#@author :yinzhengjie#blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/#EMAIL:y1053419035@qq.com
num= 1234
def revert(num,target=[]):ifnum:
target.append(num[len(num)- 1]) #等效于target.append(num[-1:])
revert(num[:len(num) - 1])returntargetprint(revert(str(num)))#以上代码执行结果如下:
['4', '3', '2', '1']
解法三
3>.解决猴子吃桃问题
猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又吃了一个。第二天早上有将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第十天早上想吃时,只剩下一个桃子了。求第一天共摘了多少个桃子。
#!/usr/bin/env python#_*_coding:utf-8_*_#@author :yinzhengjie#blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/#EMAIL:y1053419035@qq.com
def peach(days = 10):if days == 1:return 1
return (peach(days - 1) + 1) * 2
print(peach())#以上代码执行结果如下:
1534
解法一
#!/usr/bin/env python#_*_coding:utf-8_*_#@author :yinzhengjie#blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/#EMAIL:y1053419035@qq.com
def peach(days = 1):if days == 10:return 1
return (peach(days + 1) + 1) * 2
print("第一天共摘了{}个桃子".format(peach()))#以上代码执行结果如下:
第一天共摘了1534个桃子
解法二
4>.把字典扁平化
源字典 = {'a':{'b':1,'c':2}, 'd':{'e':3,'f':{'g':4}}}
目标字典= {'a.c':2,'d.e':3,'d.f.g':4,'a.b':1}
1 #!/usr/bin/env python
2 #_*_coding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
5 #EMAIL:y1053419035@qq.com
6
7
8 source = {'a':{'b':1,'c':2},'d':{'e':3,'f':{'g':4}}}9
10 target ={}11
12
13 def flatmap(src,prefix=''):14 for k,v insrc.items():15 ifisinstance(v,(list,tuple,set,dict)):16 flatmap(v,prefix=prefix + k + '.') #递归调用
17 else:18 target[prefix + k] =v19
20 flatmap(source)21
22 print(target)23
24
25
26 #以上代码输出结果如下:
27 {'a.b': 1, 'a.c': 2, 'd.e': 3, 'd.f.g': 4}
解法一
1 #!/usr/bin/env python
2 #_*_coding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
5 #EMAIL:y1053419035@qq.com
6
7
8 source = {'a':{'b':1,'c':2},'d':{'e':3,'f':{'g':4}}}9
10
11 def flatmap(src,dest=None,prefix=''):12 if dest ==None:13 dest ={}14 for k,v insrc.items():15 ifisinstance(v,(list,tuple,set,dict)):16 flatmap(v,dest,prefix=prefix + k + '.') #递归调用
17 else:18 dest[prefix + k] =v19
20 returndest21
22 target =flatmap(source)23
24 print(target)25
26
27
28 #以上代码输出结果如下:
29 {'a.b': 1, 'a.c': 2, 'd.e': 3, 'd.f.g': 4}
解法二
1 #!/usr/bin/env python
2 #_*_coding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
5 #EMAIL:y1053419035@qq.com
6
7
8 source = {'a':{'b':1,'c':2},'d':{'e':3,'f':{'g':4}}}9
10
11 def flatmap(src,dest=None,prefix=''):12 def _flatmap(src,dest=None,prefix=''):13 for k,v insrc.items():14 key = prefix +k15 ifisinstance(v,(list,tuple,set,dict)):16 _flatmap(v,dest,key + ".") #递归调用
17 else:18 dest[key] =v19 dest ={}20 _flatmap(src,dest)21 returndest22
23 target =flatmap(source)24
25 print(target)26
27
28
29 #以上代码输出结果如下:
30 {'a.b': 1, 'a.c': 2, 'd.e': 3, 'd.f.g': 4}
解法三
5>.求2个字符串的最长公共子串
1 #!/usr/bin/env python
2 #_*_coding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
5 #EMAIL:y1053419035@qq.com
6
7
8 deffindit(str1,str2):9 count =010 length =len(str1)11
12 for sublen in range(length,0,-1):13 for start in range(0,length - sublen +1):14 substr = str1[start:start +sublen]15 count += 1
16 if str2.find(substr) > -1:17 print("count={},substrlen={}0".format(count,sublen))18 returnsubstr19
20 s1 = "abcdefg"
21 s2 = "defabcdoabcdeftw"
22 s3 = "1234a"
23
24 print(findit(s1,s2))25 print(findit(s1,s3))26
27
28
29 #以上代码输出结果如下:
30 count=2,substrlen=60
31 abcdef32 count=22,substrlen=10
33 a
参考案例