一、遞迴
1. 定義
在電腦科學領域中,遞迴式通過遞迴函式來實現的。程式呼叫自身的程式設計技巧稱為遞迴( recursion)。
乙個過程或函式在其定義或說明中有直接或間接呼叫自身的一種方法,它通常把乙個大型複雜的問題層層轉化為乙個與原問題相似的規模較小的問題來求解,遞迴策略只需少量的程式就可描述出解題過程所需要的多次重複計算,大大地減少了程式的**量。遞迴的能力在於用有限的語句來定義物件的無限集合。
一般來說,遞迴需要有:邊界條件、遞迴前進段和遞迴返回段。當邊界條件不滿足時,遞迴前進;當邊界條件滿足時,遞迴返回。
注意:(1) 遞迴就是在過程或函式裡呼叫自身。
(2) 在使用遞迴策略時,必須有乙個明確的遞迴結束條件,稱為遞迴出口。
2. 問題:計算n!
數學上的計算公式為:n!=n×(n-1)×(n-2)……2×1
使用遞迴的方式,可以定義為:
3. 以遞迴方式實現階乘函式的實現:
int fact(int n)
else if((n == 0) || (n == 1))
else
}
當c程式中呼叫了乙個函式時,棧中會分配一塊空間來儲存與這個呼叫相關的資訊,每乙個呼叫都被當作是活躍的。棧上的那塊儲存空間就叫做棧幀,棧幀包含了輸入引數、返回值空間、計算表示式時用到的臨時儲存空間、函式呼叫時儲存的狀態資訊以及輸出引數。
棧是用來儲存函式呼叫資訊的絕好方案,然而棧也有一些缺點!
棧維護了每個函式呼叫的資訊直到函式返回後才釋放,這需要占用相當大的空間,尤其是在程式中使用了許多的遞迴呼叫的情況下。除此之外,因為有大量的資訊需要儲存和恢復,因此生成和銷毀活躍記錄需要消耗一定的時間。我們需要考慮採用迭代的方案,幸運的是可以採用一種稱為尾遞迴的特殊遞迴方式來避免前面提到的這些缺點。
二、尾遞迴
1. 定義
如果乙個函式中所有遞迴形式的呼叫都出現在函式的末尾,我們稱這個遞迴函式是尾遞迴的。當遞迴呼叫是整個函式體中最後執行的語句且它的返回值不屬於表示式的一部分時,這個遞迴呼叫就是尾遞迴。尾遞迴函式的特點是在回歸過程中不用做任何操作,這個特性很重要,因為大多數現代的編譯器會利用這種特點自動生成優化的**。
2. 原理
當編譯器檢測到乙個函式呼叫是尾遞迴的時候,它就覆蓋當前的活動記錄而不是在棧中去建立乙個新的。編譯器可以做到這點,因為遞迴呼叫是當前活躍期內最後一條待執行的語句,於是當這個呼叫返回時棧幀中並沒有其他事情可做,因此也就沒有儲存棧幀的必要了。通過覆蓋當前的棧幀而不是在其之上重新新增乙個,這樣所使用的棧空間就大大縮減了,這使得實際的執行效率會變得更高。雖然編譯器能夠優化尾遞迴造成的棧溢位問題,但是在程式設計中,我們還是應該盡量避免尾遞迴的出現,因為所有的尾遞迴都是可以用簡單的goto迴圈替代的。
3. 參考**
用尾遞迴求解n的階乘!
int facttail(int n, int a)
else if (n == 0)
else if (n == 1)
else
}
示例中的函式是尾遞迴的,因為對facttail的單次遞迴呼叫是函式返回前最後執行的一條語句。在facttail中碰巧最後一條語句也是對facttail的呼叫,但這並不是必需的。換句話說,在遞迴呼叫之後還可以有其他的語句執行,只是它們只能在遞迴呼叫沒有執行時才可以執行。
尾遞迴是極其重要的,不用尾遞迴,函式的堆疊耗用難以估量,需要儲存很多中間函式的堆疊。比如f(n, sum) = f(n-1) + value(n) + sum; 會儲存n個函式呼叫堆疊,而使用尾遞迴f(n, sum) = f(n-1, sum+value(n)); 這樣則只保留後乙個函式堆疊即可,之前的可優化刪去。
遞迴和尾遞迴的區別
以遞迴方式實現階加函式的實現 int recsum int n 以尾遞迴方式實現階加函式的實現 int tailrecsum int n,int res 0 遞迴 迭代 recsum 5 5 recsum 4 5 4 recsum 3 5 4 3 recsum 2 5 4 3 2 recsum 1 ...
遞迴和尾遞迴
c語言中編譯預處理的三種形式的命令 巨集定義,檔案包含,條件編譯命令。1 巨集定義主要是 define,undef 如下 define pi 3.1415926 不帶引數的巨集定義 define max a,b a b?a b 帶引數的巨集定義 說明 巨集定義在c語言與c 語言中是相通的。下面舉例說...
尾遞迴和線性遞迴
線性遞迴 fac 0 1 fac n n fac n 1 尾遞迴 fac 0,sum sum fac n,sum fac n 1,sum n 尾遞迴定義 函式最後一步呼叫自身,即最後一行 一定是對於自己的乙個遞迴呼叫。erlang尾遞迴這樣帶來的好處是可以讓編譯器做到將遞迴優化,轉化為跳轉指令而不是...