級別: ★☆☆☆☆
標籤:「算法」「遞歸」「recursion」
做者: MrLiuQ
審校: QiShare團隊php
本篇將介紹遞歸與尾遞歸的相關內容。python
遞歸是一種優雅的解決問題的方法。git
看一段最簡單的遞歸例子:github
Fibonacci
數(斐波那契數):咱們都知道Fibonacci
數的遞推公式爲:算法
用Python寫,就是這樣:bash
def Fibonacci(n):
if(n>=2):
return Fibonacci(n - 1) + Fibonacci(n - 2)
elif (n==0 or n==1):
return 1
else:
return -1
print Fibonacci(20)
複製代碼
遞歸,簡單來講,就是在運行的過程當中調用本身。微信
遞歸能幫咱們處理一些複雜的算法問題,但毫不能濫用遞歸。 在程序設計角度,循環的性能要好於遞歸。 從開發角度,使用遞歸,邏輯上更容易被理解。 因此,要分場合使用遞歸,用好遞歸。數據結構
一個遞歸的實現必定少不了基線條件和遞歸條件。函數
那麼,什麼是「基線條件」?什麼又是「遞歸條件」呢?oop
名稱 | 描述 |
---|---|
遞歸條件 | 函數調用本身的條件。 |
基線條件 | 函數再也不調用本身的條件,從而避免造成無限循環。 |
拿上面Fibonacci
的例子來講,
def Fibonacci(n):
if(n>=2):
return Fibonacci(n - 1) + Fibonacci(n - 2)
elif (n==0 or n==1):
return 1
else:
return -1
複製代碼
if(n>=2)
。elif (n==0 or n==1)
。PS:在python中,
else if
的語法是elif
。
本節涉及到了內存方面的知識——調用棧(call stack
)。
棧是一種簡單的數據結構,當咱們調用方法時,系統會執行「壓棧」操做;當咱們調用完方法時,系統會執行「出棧」操做。
簡單來講,
PS:不過還有一種特殊的狀況:叫作尾調用優化(其本質是複用棧幀,即函數調用時,再也不申請新棧幀,而是複用舊的棧幀。),在下文
3.3
節會重點講解。
咱們來看這樣一段代碼:
def func1(param1):
func2(param1)
func3(param1)
def func2(param2):
print param2
def func3(param3):
print param3
func1(647)
複製代碼
解析:定義了三個函數,分別是func1
、func2
、func3
。其中傳入的參數名爲param1
、param2
、param3
。
而在內存中,會作以下操做:
遞歸函數也會使用調用棧,咱們稱之爲「遞歸調用棧」。
下面,請看這個例子:
def factorial(x):
if x == 1:
return 1
else:
return x * factorial(x-1)
print factorial(3)
複製代碼
解析:這是一個求階乘的遞歸函數。傳入參數x,得出x*x-1...*1的值(x>=1)。 而每一次遞歸,都會申請一個棧幀,這種棧幀就叫作遞歸調用棧。
圖解以下:
尾遞歸是一種高級遞歸方式,它能夠不斷的複用舊棧幀,已達到最大的內存優化。
注意:不是全部語言都支持尾遞歸優化(尾調用優化)。
JavaScript、Objective-C、Java、C++等支持尾遞歸優化,而Python自己是不支持尾遞歸優化的。
(關於iOS中OC的尾調用優化能夠看這篇:iOS objc_msgSend尾調用優化機制詳解)
尾遞歸:在函數最後一步,僅僅返回調用了自身。(注意僅僅兩字) 尾調用:在函數最後一步,僅僅返回了一個函數。(注意僅僅兩字) 因此,尾遞歸其實是屬於尾調用的一種特殊情形。
int fun(int x) {
if (x > 0)
return fun(x-1);
else
return 1;
}
複製代碼
在函數的最後一步,僅僅return了自己的函數。符合尾遞歸。
兩張對比圖一目瞭然:
答:棧幀的重複利用。
小編微信:可加並拉入《QiShare技術交流羣》。
關注咱們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公衆號)
推薦文章:
iOS 避免常見崩潰(二)
算法小專欄:選擇排序
iOS Runloop(一)
iOS 經常使用調試方法:LLDB命令
iOS 經常使用調試方法:斷點
iOS 經常使用調試方法:靜態分析
iOS 消息轉發
奇舞週刊