python遞歸函數
什么是遞歸?
遞歸,就是在函數運行中自己調用自己
代碼示例:
def recursion(n): # 定義遞歸函數
print(n) # 打印n
recursion(n+1) # 在函數的運行種調用遞歸
recursion(1) # 調用函數
這個函數在不斷的自己調用自己,每次調用n+1,看下運行結果:
1
2
.....
998Traceback (most recent call last):
File "D:/py_study/day08-函數/python遞歸函數md/01-什么是遞歸.py", line 11, in <module>
recursion(1)
File "D:/py_study/day08-函數/python遞歸函數md/01-什么是遞歸.py", line 9, in recursion
recursion(n+1)
File "D:/py_study/day08-函數/python遞歸函數md/01-什么是遞歸.py", line 9, in recursion
recursion(n+1)
File "D:/py_study/day08-函數/python遞歸函數md/01-什么是遞歸.py", line 9, in recursion
recursion(n+1)
[Previous line repeated 993 more times]
File "D:/py_study/day08-函數/python遞歸函數md/01-什么是遞歸.py", line 8, in recursion
print(n)
RecursionError: maximum recursion depth exceeded while calling a Python object
Process finished with exit code 1
可為什么執行了900多次就報錯了呢?還說超過了最大遞歸深度限制,為什么要限制呢?
通俗來講,是因為每個函數在調用自己的時候,還沒有退出,占內存,多了肯定會導致內存崩潰.
本質上來將,在計算機中,函數調用是通過棧(stack)這樣數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會少一層棧幀.由於棧的大小不是無限的,所以,遞歸調用次數多了,會導致棧溢出.
我們還可以修改遞歸深度,代碼如下:
import sys
sys.setrecursionlimit(1500) # 修改遞歸調用深度
def cacl(n):
print(n)
cacl(n+1)
cacl(1)
運行結果如下:
1
2
......
1498Traceback (most recent call last):
File "D:/py_study/day08-函數/python遞歸函數md/02-修改遞歸深度.py", line 11, in cacl
cacl(n+1)
File "D:/py_study/day08-函數/python遞歸函數md/02-修改遞歸深度.py", line 11, in cacl
cacl(n+1)
File "D:/py_study/day08-函數/python遞歸函數md/02-修改遞歸深度.py", line 11, in cacl
cacl(n+1)
[Previous line repeated 995 more times]
File "D:/py_study/day08-函數/python遞歸函數md/02-修改遞歸深度.py", line 10, in cacl
print(n)
RecursionError: maximum recursion depth exceeded while calling a Python object
讓我們以最經典的例子說明遞歸
# 計算n! # 相信很多人都學過階乘,比如5! = 5*4*3*2*1 n! = n*(n-1)*(n-2)*...*1,那么在遞歸中該如何實現呢?
# 1.打好函數的框架
def factorial(n): # 定義一個計算階乘的函數
pass # 不做任何操作
factorial(3) # 調用
# 2.考慮兩種情況,如果n=1,那么1的階乘就是1了,如果這個傳遞的參數大於1,那么就需要計算繼承了.
def factorial(n):
if n == 1: # 判斷如果傳遞的參數是1的情況
return 1 # 返回1,return代表程序的終止
res = factorial(1) # res變量來接受函數的返回值
print(res) # 打印
2.1如果傳遞的參數不是1,怎么做?
def factorial(n):
if n == 1:
return 1
else:
# 5*4! = 5*4*3! = 5*4*3*2!
return n * factorial(n-1) # 傳遞的參數是n,那么再次調用factorial(n-1)
res = factorial(1)
print(res)
舉例2:
# 讓10不斷除以2,直到0為止。
int(10/2) = 5
int(5/2) = 2
int(2/2) = 1
int(1/2) = 0
# 1.同樣第一步先打框架
def cacl(n): # 定義函數
pass
cacl(10)
# 2.那么我們想從10開始打印然后一直到0,怎么做?
def cacl(n): # 定義函數
print(n)
cacl(10)
# 3.已經把打印的值傳遞進去了,那么就是在里面操作了
def cacl(n): # 定義函數
print(n) # 打印傳遞進去的值
v = int(n /2) # n/2
if v>0: # 如果v還大於0
cacl(v) # 遞歸,把v傳遞進去
print(n) # 打印v,因為已經調用遞歸了,所以此時的n是v
cacl(10)
運行結果如下:
10
5
2
1
1
2
5
10
怎么輸出會是這樣呢?我剛剛說過,什么是遞歸?遞歸就是在一個函數的內部調用函數本身,我們打個比方,遞歸一共有3層,那么第二層就是調用第一層的結果,第三層又去調用第二層的結果,所以!當上面這個程序運行時,第一次打印的是10,然后除上2,等於是5,再繼續除,一直到了1,然后1/2是等於0的,此時就沒有調用了遞歸,但是我還在調用上一層函數,就需要把這個數給返回出來,所以就變成后來的1,2,5,10了。
遞歸特性
- 1.必須要有一個明確的結束條件, 否則就變成死循環導致棧溢出
- 2.每次進入更深一層遞歸時,問題規模相比上次遞歸都應有所減少,這句話的以上就是說,每進入一次遞歸,就會解決一些東西,數據量就會越來越小,最終解決了所有的問題,如果進入一次遞歸沒有解決問題,那么不管遞歸多少層都沒有意義,直到導致棧溢出。
- 3.遞歸效率比較低,遞歸層次過多會導致棧溢出,意思是:每當進入一次函數調用,棧就會加一層棧幀,每當函數返回,就減少一層棧幀,由於棧不是無限大小的,所以,遞歸調用的次數過多,會導致棧溢出。
那么有沒有優化方式呢?肯定是有的
尾遞歸
我在知乎上找了一個特別有意思的例子來說明下什么是尾遞歸:
def story() {
從前有座山,
山上有座廟,
廟里有個老和尚,
一天老和尚對小和尚講故事:story() // 尾遞歸,進入下一個函數不再需要上一個函數的環境了,得出結果以后直接返回。
}
def story() {
從前有座山,
山上有座廟,
廟里有個老和尚,
一天老和尚對小和尚講故事:story(),小和尚聽了,找了塊豆腐撞死了 // 非尾遞歸,下一個函數結束以后此函數還有后續,所以必須保存本身的環境以供處理返回值。
}
尾遞歸,進入下一個函數不再需要上一個函數的環境了,得出結果以后直接返回。
def cal(n):
print(n)
return cal(n+1) # return代表函數的結束
cal(1) # 這個會一直打印,直到導致棧溢出
# 調用下一層的同時,自己就退出了
