Python-遞歸函數
阿新 • • 發佈:2018-08-24
引用 else 函數 urn pyc list end 次數 lose
閱讀目錄:
1、函數執行流程
2、遞歸Recursion
3、遞歸練習
內容:
1、函數執行流程
http://pythontutor.com/visualize.html#mode=display # 在這個網站可以輸入代碼,查看演示
或者pycharm debug查看frames
1 def foo1(b, b1=3): 2 print(‘foo1 called‘, b, b1) 3 def foo2(c): 4 foo3(c) 5 print(‘foo2 called‘, c) 6 def foo3(d): 7 print(‘foo3 called‘, d) 8 9 def main(): 10 print(‘main called‘) 11 foo1(100, 101) 12 foo2(200) 13 print(‘main ending‘) 14 15 main() 16 ------------------------------------------------- 17 main called 18 foo1 called 100 101 19 foo3 called 200 20 foo2 called 200 21 main ending
對上面的執行流程做一個簡單的描述:
- 全局幀中生成foo1,foo2 ,foo3,main函數對象
- main函數調用
- main中查找內建函數print 壓棧,將常量字符串壓棧,調用函數,彈出棧頂。
- main 中全局查找函數foo1 壓棧,將常量100 ,101 壓棧,調用函數foo1 ,創建棧幀。print函數壓棧,字符串 和 變量 b, b1 壓棧,調用函數,彈出棧頂,返回值。
- main中全局查找foo2 函數壓棧,將常量200,壓棧,調用foo2,創建棧幀。foo3 函數壓棧,變量c引用壓棧,調用foo3,創建棧幀,foo3 完成print 函數調用後返回,foo2 恢復調用,執行print後,返回值,main 中foo2 調用結束彈出棧頂,main 繼續執行print 函數調用,彈出棧頂,main返回。
圖1,函數讀取到內存中
圖2:執行main函數時,調用foo2(200)時候的棧幀圖
註:
-
- 函數執行 要壓棧,函數內執行函數,內層函數要落在外層函數上面。
- 函數結束要彈出,包括函數需要的參數等。
- 棧跟線程相關
- 定義一個函數,兩個線程分別調用該函數,各自在各自的棧空間,每個線程互不影響。
- 所以從這裏可以看到局部變量 local 函數調用時創建,調用結束消亡(調用時要壓棧,結束時彈出,之後局部變量引用不到了)
註:
1 def fn(a): 2 print(a) 3 4 g = [1] 5 6 fn(g) 7 8 # 彈出消亡只是 a,並不是g,如果引用類型,只是 地址,如上面 9 # 事實上,g=【1】是放堆裏的,開辟內存空間,受到GC管理 10 # 棧只是放引用地址
2、遞歸Recursion
- 函數直接或者間接調用自身就是遞歸
- 遞歸需要有邊界條件,遞歸前進段,遞歸返回段。
- 遞歸一定要有 邊界條件
- 當邊界條件不滿足的時候,遞歸前進
- 當邊界條件滿足的時候,遞歸返回。
2.1:斐波那契數列:1,1,2,3,5,8,13......
1 ---------------------斐波那契數列 2 # no1 最初始的版本 使用for循環完成,交換 3 def fib(n): 4 a = 0 5 b = 1 6 for i in range(n): 7 a, b = b, a + b 8 return a 9 print(fib(3)) # 2 10 11 # no2.1 使用遞歸完成,但是這個版本的 遞歸效率極低,通過 ipython的jupyter 的%%timeit測試, 還不如循環。因為return的fib(n-1)+fib(n-2),d都要分別往下繼續執行,數據量大的時候,這個相當於重復了很多,前面算了的,後面fib(n-2)裏還要算一次,所以效率極低。 12 def fib(n): 13 if n < 3: 14 return 1 15 return fib(n-1) + fib(n-2) 16 17 print(fib(3)) # 2 18 19 # no2.2 變形版本: 20 def fib(n): 21 return 1 if n < 3 else fib(n-1) + fib(n-2) 22 23 print(fib(3)) # 2 24 25 # no 3 利用上次的結果的遞歸 26 # 這裏使用 缺省參數作為運算結果,而n 作為循環次數控制量 27 def fib(n, a=0, b=1): def fib(n, a=0, b=1): def fib(n, a=0, b=1): 28 if n == 0: if n == 0: if n == 0: 29 return a return a return a 30 a, b = b, a + b a, b = b, a + b a, b = b, a + b 31 return fib(n-1, a, b) return fib(n-1, a, b) return fib(n-1, a, b) 32 33 print((fib(3)))斐波那契數列遞歸以及最初版本實現
2.2:各個解釋器對針對遞歸的保護機制:到達這個數據,就會拋異常,不管你是不是還要進行!
CPython中:
import sys
print((sys.getrecursionlimit())) # 1000
IPython中: 是3000
註: 但是事實上是不夠這個數字,因為當前棧不可能只有自己的函數,可能包含其他的數據,基礎性數據等。會占一部分棧空間。
遞歸要求:
-
-
- 遞歸一定要有退出條件,遞歸調用一定要執行到這個退出條件。沒有退出條件的遞歸調用,就是無限調用。
- 遞歸調用的深度不宜過深
- Python對遞歸調用的深度做了限制,以保護解釋器
- 超過遞歸深度限制,拋出 RecursionError:maxinum recursion depthexceeded 超出最大深度
- sys,getrecursionlimit()
-
遞歸性能:
-
-
- for循環,遞歸兩者的性能對比,事實上,for循環的性能比遞歸性能要強,尤其數據量大的時候。
- 循環稍微復雜一些,但是只要不是死循環,可以多次叠代直至算出結果
- fib 函數代碼簡潔易懂,但是只有獲取到嘴歪層的函數調用,內部遞歸結果都是中結案結果,而且給定一個n都要進行2n次遞歸,深度 越深,效率月底,為了獲取斐波那契數列需要外面在套一個N次的循環,效率就更低了,但是for循環的空間復雜度比較高。
- 遞歸還有深度極限,如果遞歸復雜,函數反復壓棧,棧內存很快就溢出了。
-
節間遞歸:
def foo1():
foo2()
def foo2():
foo1()
foo1()
間接遞歸,是通過別的函數調用了函數本身
但是,如果構成了循環遞歸調用時非常危險的,但是往往這種情況在代碼復雜的情況下,還是可能發生這種調用,要用代碼的規範來避免這種 遞歸調用的發生。
遞歸總結:
-
-
- 遞歸是一種很自然的表達,符合邏輯思維
- 遞歸相對運行效率低,每一次調用函數都要開辟棧幀,每次要壓棧,但是效率比較低。
- 遞歸有深度限制,如果遞歸層次太深,函數反復壓棧,棧內存很快就溢出了。
- 如果是有限次數的遞歸,可以使用遞歸調用,或者使用循環替代,循環代碼稍微復雜些,但是只要不是西孫煥可以多次叠代直至算出結果。
- 絕大多數遞歸,都可以使用循環實現。
- 線上,能不用則不用。
-
3、遞歸練習
https://www.cnblogs.com/JerryZao/p/9531951.html
Python-遞歸函數