演算法小專欄:遞迴與尾遞迴

QiShare發表於2019-04-01

級別: ★☆☆☆☆
標籤:「演算法」「遞迴」「recursion」
作者: MrLiuQ
審校: QiShare團隊


本篇將介紹遞迴尾遞迴的相關內容。

一、什麼是“遞迴”?

遞迴是一種優雅的解決問題的方法。

看一段最簡單的遞迴例子:

Fibonacci數(斐波那契數):我們都知道Fibonacci數的遞推公式為:

  • F(0)=F(1)=1,
  • 當n>=2時,F(n)=F(n-1)+F(n-2)

用Python寫,就是這樣:

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)
複製程式碼

遞迴,簡單來說,就是在執行的過程中呼叫自己。

遞迴能幫我們處理一些複雜的演算法問題,但絕不能濫用遞迴。 在程式設計角度,迴圈的效能要好於遞迴。 從開發角度,使用遞迴,邏輯上更容易被理解。 所以,要分場合使用遞迴,用好遞迴。

二、基線條件和遞迴條件

一個遞迴的實現一定少不了基線條件遞迴條件

那麼,什麼是“基線條件”?什麼又是“遞迴條件”呢?

名稱 描述
遞迴條件 函式呼叫自己的條件。
基線條件 函式不再呼叫自己的條件,從而避免形成無限迴圈。

拿上面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節會重點講解。

3.1 呼叫棧

我們來看這樣一段程式碼:

def func1(param1):
    func2(param1)
    func3(param1)

def func2(param2):
    print param2

def func3(param3):
    print param3

func1(647)
複製程式碼

解析:定義了三個函式,分別是func1func2func3。其中傳入的引數名為param1param2param3

而在記憶體中,會做如下操作:

演算法小專欄:遞迴與尾遞迴

3.2 遞迴呼叫棧

遞迴函式也會使用呼叫棧,我們稱之為“遞迴呼叫棧”。

下面,請看這個例子:

def factorial(x):
    if x == 1:
        return 1
    else:
        return x * factorial(x-1)

print factorial(3)
複製程式碼

解析:這是一個求階乘的遞迴函式。傳入引數x,得出x*x-1...*1的值(x>=1)。 而每一次遞迴,都會申請一個棧幀,這種棧幀就叫做遞迴呼叫棧

圖解如下:

演算法小專欄:遞迴與尾遞迴

3.3 尾遞迴

尾遞迴是一種高階遞迴方式,它可以不斷的複用舊棧幀,已達到最大的記憶體優化。

注意:不是所有語言都支援尾遞迴優化(尾呼叫優化)。
JavaScript、Objective-C、Java、C++等支援尾遞迴優化,而Python本身是不支援尾遞迴優化的。
(關於iOS中OC的尾呼叫優化可以看這篇:iOS objc_msgSend尾呼叫優化機制詳解

Q1:什麼是尾遞迴?什麼又是尾呼叫?

尾遞迴:在函式最後一步,僅僅返回撥用了自身。(注意僅僅兩字) 尾呼叫:在函式最後一步,僅僅返回了一個函式。(注意僅僅兩字) 所以,尾遞迴實際上是屬於尾呼叫的一種特殊情形

Q2:舉個尾遞迴的例子?
int fun(int x) {
  if (x > 0)
    return fun(x-1);
  else
    return 1;
}
複製程式碼

在函式的最後一步,僅僅return了本身的函式。符合尾遞迴。

Q3:尾遞迴究竟做了什麼優化?

兩張對比圖一目瞭然:

  • 非尾呼叫:

演算法小專欄:遞迴與尾遞迴

  • 是尾呼叫:

演算法小專欄:遞迴與尾遞迴

Q4:尾遞迴的本質是什麼?

答:棧幀的重複利用。


小編微信:可加並拉入《QiShare技術交流群》。

演算法小專欄:遞迴與尾遞迴

關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)

推薦文章:
iOS 避免常見崩潰(二)
演算法小專欄:選擇排序
iOS Runloop(一)
iOS 常用除錯方法:LLDB命令
iOS 常用除錯方法:斷點
iOS 常用除錯方法:靜態分析
iOS 訊息轉發
奇舞週刊

相關文章