优化递归的效率
函數遞歸調用是很常見的做法,但是它往往是低效的,本文探討優化遞歸效率的思路。
1.尾遞歸轉換成迭代
尾遞歸是一種簡單的遞歸,它可以用迭代來代替 比如 求階乘函數的遞歸表達
int?f(int?n){
????if(n<=0)return?1;
????return?n*f(n-1);
}
可以轉換成完全等價的循環迭代
int?f(int?n){
????int?r=0;
????while(n-->0)
????????r*=n;
????return?r;
}
尾遞歸是最簡單的情形,好的編譯器甚至可以自動的識別尾遞歸并把它轉換成循環迭代。
2.動態規劃
我一直把動態規劃看作尾遞歸的推廣(個人觀點),在2維或更高的情況下,直接使用遞歸會造成大量的重復計算,例如在斐波那契數列的遞歸關系 Fib(n+2)=Fib(n+1)+Fib(n)中 Fib(3)在計算Fib(4)和Fib(5)都會用到 他被重復計算了2遍,當數列長度增大時 這種浪費會變得越來越明顯。
動態規劃方法將可能需要的結果全部計算出來 并保存 一般來說 動態規劃從最小數開始 自底向上計算所有值(因為后面的結果會用到前面的結果) 直到得到的結果中包含了要求的結果。
int?Fib(unsigned?n){
????if(n==1)return?1;
????if(n==0)return?0;
????return?Fib(n-1)+Fib(n-2);
}
int?Fib(unsigned?n)
{
????int*?f=new?int[n+1];
????f[1]=1;f[0]=0;
????for(int?i=0;i<=n;i++);
????????f[i]=f[i-1]+f[i-2];??
????int?r=f[n];
????delete[]?f;
????return?r;
}
動態規劃不會造成重復運算 但是 它可能計算不需要的結果 例如關系式
? ?? a(n)=a(n-2)+a(n-4);
使用動態規劃會計算很多不需要的結果,盡管如此,它的效率遠遠高于直接遞歸運算。
實際上,大部分時候動態規劃把指數級時間復雜度的遞歸運算變成了多項式級時間復雜度的遞推。
3.備忘錄
減少重復值的另一個方法是使用備忘錄,每次成功計算一個結果 ,就將它存入備忘錄中,當再次遇到此問題時,無需重復計算,直接取出即可。
備忘錄方法和直接遞歸相似,只是在函數在計算之前先訪問備忘錄,如果在備忘錄中找到,就無須再計算,直接返回。
備忘錄結構可以使用關聯數組 哈希表等實現,特別地,當遞歸參數是整數時 直接用數組就可以了。
與動態規劃相比,備忘錄消耗了額外的備忘錄查找時間,并且和直接遞歸一樣 有大量的多余函數調用開銷,但它不會造成額外計算。
4.內聯
這是來自Efficient C++的方法,C++編譯器不會把遞歸函數內聯,這樣,函數調用的開銷變得很大。為了提高效率,必須手動內聯函數。
遞歸函數無法完全內聯,但是我們可以把它展開,這是前面Fibnacci函數的一層展開
{
????if(n==1)return?1;
????if(n==0)return?0;
????int?fn1,fn2;
????if(n-1==1)fn1=1;
????else?if(n-1==0)fn1=0;
????else?fn1=Fib(n-2)+Fib(n-3);
????if(n-2==1)fn2=1;
????else?if(n-2==0)fn2=0;
????else?fn2=Fib(n-3)+Fib(n-4);
????return?fn1+fn2
}
?5.解遞歸
盡管我們傾向于虐待計算機 讓它幫我們處理較復雜的問題,但是很多時候我們需要獲得效率,就必須自己動手,其實很多遞歸式可以手動解出,組合數學為我們提供了不少工具:
???? (1)解齊次遞歸方程
簡單的遞歸方程可以按以下步驟求解:
from: http://www.cnblogs.com/winter-cn/archive/2008/08/23/1274475.html
總結
- 上一篇: 今天碰到一道比较有趣的面试题,大家来探讨
- 下一篇: 谈谈面试与面试题