深刻掌握遞歸——從棧圖來認識

函數調用另外一個函數是合法的;函數調用自己也是合法的。調用自己的過程稱爲遞歸函數,這個執行過程叫做遞歸

遞歸在數據結構中經常會用到,特別是解決樹的遞歸問題時很好用。但是想明白遞歸是挺燒腦的,一般即使兩層、三層遞歸也會容易給人繞進去。要是我們瞭解函數在底層的存儲機制,利用棧(先進後出)來進行分析,或許就容易多了。

不講廢話,直接撈乾的,我們首先回憶下遞歸的規則,

函數遞歸調用的重要規則

  1. 程序執行一個函數時,就創建一個新的受保護的獨立空間(新函數棧)

  2. 函數的局部變量是獨立的,不會相互影響

  3. 遞歸必須向退出遞歸的條件逼近,否則就是無限遞歸,死龜了:)

  4. 當一個函數執行完畢,或者遇到 return,就會返回,遵守誰調用,就將結果返回給誰。

分析遞歸流程

我們一個例子來快速熟悉遞歸:

#求函數值:
已知 f(1)=3; f(n) = 2*f(n-1)+1;
請使用遞歸的思想編程,求出f(n)的值.

編寫程序

# coding=utf-8
# -*- coding:utf-8 -*-
'''
Author: Solorzhou
Email: [email protected]

date: 2019/10/26 20:13
desc:
'''

def f(n):
    if n == 1:
        return 3
    else:
        return 2 * f(n - 1) + 1
        print("n=", n)


if __name__ == '__main__':
    f(4)

遞歸函數的棧圖

在這裏插入圖片描述
可以看到,一個函數每次被調用時,都會在內存中創建一個幀,來包含函數的局部變量和參數,對於遞歸函數,棧上可能同時存在多個函數幀。當每調用一次函數f(n)時,棧頂指針就會往棧頂移動一個位置,直到滿足退出遞歸的條件(n==1).之後再依次返回當前的結果直接,棧頂指針往下移動。

遞歸,也就是先“遞”後“歸”。

本題則是:

f(4)=2 * f(3) + 1
f(4)=2 * (2 * f(2) + 1) + 1
f(4)=2 * (2 * (2 * f(1) + 1) + 1) + 1
f(4)=2 * (2 * (2 * 3 + 1) + 1) + 1
f(4)=2 * 15 + 1
f(4)=31

一些例子

  • 1.斐波那契數列

請使用遞歸的方式,求出斐波那契數 1,1,2,3,5,8,13…
給你一個整數 n,求出它的斐波那契數是多少?

  • 2 猴子吃桃子問題

有一堆桃子,猴子第一天吃了其中的一半,並再多吃了一個!以後每天猴子都吃其中的一半,然後再
多吃一個。當到第十天時,想再吃時(還沒吃),發現只有 1 個桃子了。問題:最初共多少個桃子?

這些遞歸問題,當我們使用棧圖來思考時就容易多了。

堅定信念

跟蹤程序執行的流程是閱讀程序的一個方法,但是這樣很快就會陷入到迷宮境況。之前在一本書中看到作者對於遞歸給出了一種“堅定信念”的思想。在遇到一個程序時,不去跟蹤執行的流程,而是假定函數都是正確工作的,都能夠返回正確的結果。

事實上,在使用內置函數時,我們已經在嘗試這樣堅持信念了。如:當調用math.cos或者math.exp時,我們並不會去檢查函數的內部實現。因爲我們假定他是正確的,因爲些內置函數的程序員肯定都很優秀。

當調用自己寫的函數時,假定它也是正確的,不需要檢查他的執行流程。看一個關於斐波那契數列的例子:

fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(2) = fibonacci(0) +fibonacci(1)

翻譯成python語言,看起來是這樣:

def fibonacci(n):
    if n ==0:
        return 0
    elif n ==1:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)

假定寫的程序是正確的,那麼很明顯第三個數等於前兩個數字之和。以上程序運行後肯定也是正確的。

從一名不羈的碼農開始,談風月之餘談技術

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章