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("\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 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("\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


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("\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


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
參考案例

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM