Python入门篇-递归函数Recursion

            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 import sys
 8 
 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 def fib(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是:100037 当前递归调用次数限制是:2000038 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 import datetime
 9 
10 start = datetime.datetime.now()
11 
12 pre = 0
13 
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 + cur
22     print(cur, end=' ')
23 
24 delta = (datetime.datetime.now() - start).total_seconds()
25 
26 print("
递归调用打印斐波拉契钱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 import datetime
 8 
 9 n = 35
10 
11 start = datetime.datetime.now()
12 
13 def fib(n):
14     return 1 if n < 2 else fib(n-1) + fib(n-2)
15 
16 for i in range(n):
17     print(fib(i), end=' ')
18 
19 delta = (datetime.datetime.now() -start).total_seconds()
20 
21 print("
递归调用打印斐波拉契钱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


import datetime

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 + cur
    print(cur, end=' ')
    if n == 2:
        return
    fib(n-1, pre, cur)

fib(35)

delta = (datetime.datetime.now() -start).total_seconds()

print("
递归调用打印斐波拉契钱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


def foo():
    bar()

def bar():
    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


def fac(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:
        return p
    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 is None:
       p = [1]
   if n == 1:
       return p[0]

   p[0] *= n
   return 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)

def revert(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 is None:
        list_1 = []
    x,y = divmod(n,10)
    list_1.append(y)

    if x == 0:
        return list_1
    return revert(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=[]):
    if num:
        target.append(num[len(num) - 1])    #等效于target.append(num[-1:])
        revert(num[:len(num) - 1])
    return target

print(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 in src.items():
15         if isinstance(v,(list,tuple,set,dict)):
16             flatmap(v,prefix=prefix + k + '.')      #递归调用
17         else:
18             target[prefix + k] = v
19 
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 in src.items():
15         if isinstance(v,(list,tuple,set,dict)):
16             flatmap(v,dest,prefix=prefix + k + '.')      #递归调用
17         else:
18             dest[prefix + k] = v
19 
20     return dest
21 
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 in src.items():
14             key = prefix + k
15             if isinstance(v,(list,tuple,set,dict)):
16                 _flatmap(v,dest,key + ".")              #递归调用
17             else:
18                 dest[key] = v
19     dest = {}
20     _flatmap(src,dest)
21     return dest
22 
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 def findit(str1,str2):
 9     count = 0
10     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                 return substr
19 
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 abcdef
32 count=22,substrlen=10
33 a
参考案例
原文地址:https://www.cnblogs.com/yinzhengjie/p/10961457.html