演算法精解十六(C語言版)

2021-06-28 18:14:44 字數 1772 閱讀 9813

尾遞迴

如果乙個函式所有遞迴形成的呼叫都出現在函式的末尾,我們稱這個遞迴函式是尾遞迴的。當遞迴呼叫是整個函式中最後執行的語句且它的返回值不屬於表示式的一部分時,這個遞迴呼叫就是尾遞迴。尾遞迴函式的特點是在回歸過程中不用做任何操作,這個特性很重要,因為大多數現代的編譯器會利用這個特點自動生成優化的**。

當編譯器檢測乙個函式嗲用是尾遞迴的時候,它就覆蓋當前的活躍記錄而不是在棧中去建立乙個新的,編譯器可以做到這點,因為遞迴呼叫時當前活躍期內最後一條待執行的語句,於是當這個呼叫返回時棧幀中並沒有其他事情可做,因此也就是沒有儲存棧幀的必要了,通過覆蓋當前的棧幀而不是在其之上重新新增乙個,這樣所使用的棧空間就大大縮減了,這使用得實際的執行效率會變得更高。因此,只要有可能我們就需要將遞迴函式寫成尾遞迴的形式。

為了理解尾遞迴是如何工作的,讓我們再次以遞迴的計算階乘。首先,這可以很容易讓我們理解為什麼之前所定義的遞迴不是尾遞迴。會議之前對計算n!的定義:在每個活躍期計算n倍的(n-1)!的值,讓n=n-1並持續這個過程直到n=1為止。這種定義不是尾遞迴的,因為每乙個活躍期的返回值都依賴於用n乘以下乙個活躍期的返回值,因此每次呼叫產生的棧幀將不得不儲存在棧上直到下乙個子呼叫的返回值確定。現在讓我們考慮以尾遞迴的形式來定義計算n!的過程。函式可以定義成如下形式:

這種定義還需要接受第二個引數a,除此之外並沒有太大區別。a(初始化為1)維護遞迴層次的深度。這就讓我們避免了每次還 需要將返回值再乘以n.然而,在每次遞迴呼叫中,令a=na並且n=n-1.繼續遞迴呼叫,直到n-1, 這滿足結束條件,此時直接返回值a即可。圖3-4說明了用尾遞迴計算4!的過程。注意的在回歸過程中不需要做任何操作,這是所有尾遞迴函式的標誌。

**示例3-2給出了乙個c函式facttail,它接受乙個整數n並以尾遞迴的形式計算n的階乘。這個函式還接受乙個引數a,a的值初始值為1.facttail使用a來維護遞迴層次的深度,除此之外它和fact很相似。讀者可以注意一下函式的具體實現和尾遞迴定義的相識之處。

示例3-2中的函式是尾遞迴的,因為對fattil的單次遞迴呼叫時函式返回前最後執行的一條語句。在facttail中碰巧最後一條語句也是對facttil的呼叫,但這並不是必需的。換句話說,在遞迴覅用之後還可以有其他的語句執行,只是它們只能在遞迴呼叫沒有執行時才可以執行。圖3-5展示了當使用尾遞迴函式計算4!時棧使用的情況,讀者可以和圖3-3展示的棧使用情況作對比。

演算法精解一(C語言版)

最近無事 抽出點時間來整理一下演算法 希望對自己有進一步的幫助和對學習演算法的同行有一些幫助吧!講到演算法 這個詞是很重乙個解決問題的途徑 無論在什麼行業 演算法都是很重要的。不管你是否承認,無論哪種軟體開發專案,幾乎所有的程式設計師,開發者在日常工作中都要同資料結構和演算法打交道。當我們閱讀原始碼...

演算法精解五(C語言版)

指標操作 在c語言中,對於任何型別t,我們都可以在t所在的記憶體位址處產生乙個包含對此物件位址的對應變數。如果用比較直觀方式來看待這種變數,它們實際上是一種指向物件的變數,如果用比較直觀方式來看待這種變數,它們實際上是一種指向物件的變數 因此,這些變數稱為指標。在c語言中,指標是構建資料結構和操作記...

演算法精解七(C語言版)

儲存空間分配 擋在c中宣告乙個指標時,與宣告其他型別的變數類似,一定量的儲存空間會分配給這個指標。通常情況下指標會占用乙個機器字長的儲存空間,但有些時候他們的大小也有所不同。因此 為了保證 的可移植性,不應該假設每個指標都占有乙個特定大小的儲存空間,指標變數的大小通常與編譯器的設定以及某些特定的c實...