算法小專欄:遞歸與尾遞歸

級別: ★☆☆☆☆
標籤:「算法」「遞歸」「recursion」
做者: MrLiuQ
審校: QiShare團隊php


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

1、什麼是「遞歸」?

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

看一段最簡單的遞歸例子:github

Fibonacci數(斐波那契數):咱們都知道Fibonacci數的遞推公式爲:算法

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

用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)
複製代碼

遞歸,簡單來講,就是在運行的過程當中調用本身。微信

遞歸能幫咱們處理一些複雜的算法問題,但毫不能濫用遞歸。 在程序設計角度,循環的性能要好於遞歸。 從開發角度,使用遞歸,邏輯上更容易被理解。 因此,要分場合使用遞歸,用好遞歸。數據結構

2、基線條件和遞歸條件

一個遞歸的實現必定少不了基線條件遞歸條件函數

那麼,什麼是「基線條件」?什麼又是「遞歸條件」呢?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

3、棧

本節涉及到了內存方面的知識——調用棧(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 消息轉發
奇舞週刊

相關文章
相關標籤/搜索