一、什麼是“遞歸”
遞歸,顧名思義,包括“遞”和“歸”兩個過程,“遞”的過程就是將原問題一層一層分解下去直到找到終止條件,“歸”的過程就是從終止條件開始一層一層向上迴歸直到歸併到原問題。
可以看出,遞歸需要滿足三個條件:
- 原問題能夠分解爲若干個子問題;
- 子問題與原問題求解思路相同,只是數據規模不同;
- 存在終止條件;
一個經典的遞歸問題是:
假如這裏有 n 個臺階,每次你可以跨 1 個臺階或者 2 個臺階,請問走這 n 個臺階有多少種走法?
求解該問題,可按照遞歸三個條件來進行:
- 原問題可以分解爲兩個子問題:下一步走一個臺階、下一步走兩個臺階;
- 子問題與原問題求解思路相同,只是數據規模不同:;
- 存在終止條件:;
二、Python使用遞歸實現走臺階
基於上述原理,可以輕鬆實現一個遞歸代碼:
def walk_steps(n:int)->int:
if n == 1:
return 1
elif n == 2:
return 2
return walk_steps(n-1) + walk_steps(n-2)
print(walk_steps(7))
三、遞歸轉循環
遞歸編程思想簡單,幾行代碼即可實現一個人腦思考不過來的遞歸問題,但卻有着致命缺點:堆棧溢出、空間複雜度高!
在編程語言進行函數調用時,採用棧來存放臨時變量,因此對於遞歸函數,其子問題的返回值都會臨時存放在棧中,當數據規模變大時,其空間複雜度將極大地提高,容易造成堆棧溢出,函數求解將變得極其困難。
對於上述實現的遞歸代碼,在小數據量時,感覺不到困難,但當數據規模(即總的臺階數n)變大時(大概超過40時)求解將變得極其緩慢,且繼續增大到一定規模,就會報堆棧溢出的錯誤:
RecursionError: maximum recursion depth exceeded in comparison
因此,對於某些遞歸函數,如果可以轉化爲非遞歸,則能大大降低其空間複雜度,同時避免堆棧溢出。下面使用循環方式實現階梯走法計算:
def walk_steps_circle(n:int)->int:
if n == 1:
return 1
elif n == 2:
return 2
step1 = 1
step2 = 2
step_total = 0
for i in range(3,n+1):
step_total = step1 + step2
step1 = step2
step2 = step_total
return step_total
walk_steps_circle(7)
大家可以在自己機器上試一下,分別給兩個函數傳入一個較大的參數,看看運行結果,即可發現其明顯的差別。