計算機系統學習3 函式遞迴

2021-07-27 23:52:55 字數 3260 閱讀 5490

遞迴演算法具有很好的可讀性和可維護性。所謂遞迴,是指利用分而治之的思想,將乙個複雜的問題,不斷簡化成簡單的易於處理的同型別的問題。

乙個典型的遞迴包括以下2個部分:

recursive case:將乙個複雜問題不斷簡化為乙個同型別的易於處理的簡單問題

base case:不斷簡化,一直簡化到乙個可以直接處理的問題

遞迴的步驟類似於數學證明中的數學歸納法,因此可以用數學歸納法證明遞迴的正確性。

我們以計算n的階乘的例子來看看函式遞迴的基本情況:

示例:計算n!

int factorial(int n)else

}

從這個例子中,我們可以看到遞迴通過不斷的呼叫自己(recursive case),實現將乙個複雜的問題,簡化成乙個計算量小的簡單問題,直到可以直接得到結果的那步(base case)。

那麼,遞迴在記憶體中是如何執行的呢?

乙個程序在記憶體中的布局如圖所示:

在這個圖中,階乘函式編譯後會被放在**段,而其中的每個棧幀則代表被呼叫中的乙個函式,具體的函式呼叫的過程,在計算機系統學習2:程式的機器級表示之函式呼叫這篇文章中進行了詳細的介紹。遞迴在呼叫函式的過程中,始終呼叫的是同一函式,因此,**段中,只需要放一段遞迴函式就可以了。

接下來,我們看看,遞迴的輸入和返回的執行過程。以factorial(4)為例:

首先在計算factorial(4)的時候,形成乙個函式棧幀,返回的是4*factorial(3),但是factorial(3)不知道是多少,因此,需要再形成新的函式棧幀,計算factorial(3),即返回3*factorial(2),接著以同樣的方式計算factorial(2),如此迴圈,一直到base case:factorial(1),直接返回1,然後每個函式棧幀逐個出棧,最終得到factorial(4)的值。

如圖所示:

遞迴雖然好用,但是,由於記憶體容量有限,因此,如果遞迴太深,或者遞迴的量很大的話,就會造成記憶體溢位。

那麼如何解決這個問題呢?

首先我們分析一下,上面提出的這個階乘的計算方式,我們會發現,n*factorial(4)在記憶體中執行的時候,每個函式棧幀都要記錄下當前的n的值,同時還要記錄其返回值,然後才能計算出當前棧幀的結果,記錄不同的n值的時候,我們就需要不同的函式棧幀,因此,如果我們可以想辦法設計乙個方法,不再儲存n的的值,而只保留輸入引數和返回值,從而實現函式棧幀的復用。

**如下:

int factorial(int n,int result)else

}

其計算過程如下:

我們注意到,在這種方法中,recursive case中,不再是n*factorial(4)了,而是直接呼叫函式factorial(...)本身。

在記憶體執行的過程為:

由於不用再記錄表示式n*factorial(4)中的不同的n,而是直接呼叫函式factorial(...)自身,這種方法可以在遞迴中,完全復用同乙個棧幀,而不用開闢新的函式棧幀。

這種遞迴方法就是尾遞迴。所謂的尾遞迴,就是指在乙個遞迴中,遞迴呼叫是函式體中最後執行的語句,並且它的返回值不屬於這個表示式一部分時,那麼這個遞迴就可以稱為尾遞迴。尾遞迴函式的最後乙個動作是呼叫函式本身,是遞迴的一種特殊情形。

尾遞迴具有2個主要的特徵:

呼叫自身函式(self-called);

計算僅占用常量棧空間(stack space).

現代的編譯器會發現尾遞迴的這個特點, 生成優化的**, 復用棧幀。 第乙個演算法中因為有個n * factorial(n-1), 雖然也是遞迴, 但是遞迴的結果處於乙個表示式中, 還要做計算, 所以就沒法復用棧幀了, 只能一層一層的呼叫下去。

1. fibonacci numbers

#fibonacci numbers

deffib

(x):

"""assumes x an int >= 0

returns fibonacci of x"""

#assert type(x) == int and x >= 0

if x == 0

or x == 1:

return

1else:

return fib(x-1) + fib(x-2)

2. towers of hanoi
#towers of hanoi

defprintmove

(fr, to):

print('move from ' + str(fr) + 'to' + str(to))

deftowers

(n, fr, to, spare):

if n==1:

printmove(fr, to)

else:

towers(n-1, fr, spare, to)

towers(1, fr, to, spare)

towers(n-1, spare, to, fr)

/*

*towers(1,'f', 't', 's')

*towers(2,'f', 't', 's')

*towers(5,'f', 't', 's')

*/

參考資料水平有限,錯誤和不妥之處請指出,謝謝。

計算機系統學習筆記 gcc和gdb

gcc main.c 會預設生成乙個a.out的可執行檔案 gcc main.c o 會產生乙個 hello 可執行檔案 gcc main.c o 會產生乙個 可執行檔案 詳細過程可以分為四步 可以手動進行這四個步驟 gcc e main.c 不會產生.i檔案 gcc e main.c main.i...

計算機系統基礎 學習記錄3

捨入方式 1 向零捨入。例如 2.1捨入到 2,3.4捨入到3 2 向下 負無窮 捨入 3 向上 正無窮 捨入 4 向偶數捨入 向離自己最近的整數捨入,靠兩個整數的中間時,向偶數捨入 經過證明可以得到,向偶數捨入的方式誤差最小,因此採用此種方式 二進位制的捨入 在二進位制的捨入中,如果將要被捨去的首...

計算機系統

一陰一陽之為道。早在幾千年我國古人就知道天地萬物皆由正反兩個東西組成的。自從人類進入電氣時代,隨著電子元件的快速發展,各種裝置隨之誕生了。一些元件 例如閘流體 可以根據電壓的高低變化自行導 通或者關斷。如果是高電壓導通,就規定是1 低電壓導通規定是0,那麼眾多的這些元件組成的乙個整體就可以0 和1來...