递归优化的这三种方式你知道吗?
估計找工作的,都會碰到面試官老是問道“遞歸算法”,感同身受,前段時間面試的時候,就有一家問道這個問題,是非常典型的問題。在前面一篇世界上有哪些代碼量很少,但很牛逼很經(jīng)典的算法或項目案例?,遞歸應(yīng)該算是比較“經(jīng)典”的算法。
1.從 斐波那契數(shù)列開始說起
波那契數(shù)列指的是這樣一個數(shù)列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,是指這樣一個數(shù)列
遞推公式如圖:
遞推公式有沒有直接計算的公式?當(dāng)然有
public int Fibo(int n) {double c = Math.Sqrt(5);return (int)((Math.Pow((1 + c) / 2, n) - Math.Pow((1 - c) / 2, n)) / c); }1.最常見的遞歸
第一項和第二項的值均為1,后面每項的值都是前兩項值之和,所以我們很多人基本上都會使用遞歸來實現(xiàn),常見的算法如下:
public int Fibo(int n){if (n == 1 || n == 2){return 1;}return Fibo(n - 2) + Fibo(n - 1);}但這種做法并不能完全解決問題,因為最大允許的遞歸深度跟當(dāng)前線程剩余的棧空間大小有關(guān),事先無法計算。如果實時計算,代碼過于復(fù)雜,就會影響代碼的可讀性。所以,如果最大深度比較小,比如 10、50,就可以用這種方法,否則這種方法并不是很實用。
ps:遞歸代碼要警惕重復(fù)計算
除此之外,使用遞歸時還會出現(xiàn)重復(fù)計算的問題。剛才我講的第二個遞歸代碼的例子,如果我們把剛才我講的第二個遞歸代碼的例子,如果我們把整個遞歸過程分解一下的話,那就是這樣的:n 越大,這段代碼執(zhí)行效率越低通過測試一下看他的效率如何
Stopwatch sw = new Stopwatch();sw.Start();var result = Fibo(40);sw.Stop();Debug.WriteLine("n=100;result="+result+";耗時:"+sw.ElapsedMilliseconds); n=10;result=55;耗時:2715 n=40;result=102334155;耗時:4673如果n再稍微大一點,所消耗的時間是成指數(shù)級增長的,比如n=64的時候,所消耗的時間可能是兩三年!不信的話,你可以試試!
我們會發(fā)現(xiàn)f(n)這個方法被調(diào)用了很多次,而且其中重復(fù)率非常之高,也就是說被重復(fù)計算了很多次,如果n稍微大一點這棵樹會非常龐大。這里我們可以看出,每個節(jié)點就需要計算一次,總計算的次數(shù)就是該二叉樹節(jié)點的數(shù)量,可見其時間復(fù)雜度為O(2n),是指數(shù)級的,其空間復(fù)雜度也就是該二叉樹的高度,為O(n)。這樣來看,我們應(yīng)該就清楚了,為什么這段代碼效率如此低下了吧。
2.數(shù)組保存法
我們應(yīng)該避免無數(shù)次重復(fù)的計算
為了避免無數(shù)次重復(fù),可以從n=1開始往上計算,并把每一個計算出來的數(shù)據(jù),用一個數(shù)組保存,需要最終值時直接從數(shù)組中取即可,算法如下:
public int fib(int n) {int[] fib = new int[n];fib[0] = 1;fib[1] = 1;for (int i = 2; i < n; i++) {fib[i] = fib[i - 2] + fib[i - 1];}return fib[n - 1]; }測試一下結(jié)果
n=10;result=55;耗時:0 n=40;result=102334155;耗時:0 n=1000000;result=1884755131;耗時:5毫秒級,幾乎忽略不計的,當(dāng)計算100萬時,也就5毫秒
3.滾動數(shù)組法
盡管上述算法已經(jīng)很高效了,但我們還是會發(fā)現(xiàn)一個問題,其實整個數(shù)組中,每次計算時都只需要最新的3個值,前面的值計算完后就不再需要了。比如,計算到第10次時,需要的數(shù)組空間只有第8和第9兩個空間,前面第1到第7個空間其實就不再需要了。所以我們還可以改進(jìn),通過3個變量來存儲數(shù)據(jù),算法如下:
public int Fibo3(int n){int first = 1;int second = 1;int third = 2;for (int i = 3; i <= n; i++){third = first + second;first = second;second = third;}return third;}時間復(fù)雜度仍然為O(n),而空間復(fù)雜度為常量級別3,即空間復(fù)雜度為0,所以這種方法是非常高效的。
3.尾遞歸法
首先我們來了解一下什么是尾調(diào)用。
在計算機(jī)科學(xué)里,尾調(diào)用是指一個函數(shù)里的最后一個動作是一個函數(shù)調(diào)用的情形:即這個調(diào)用的返回值直接被當(dāng)前函數(shù)返回的情形。這種情形下該調(diào)用位置為尾位置。
/// n 第n個數(shù)/// first 第n個數(shù)/// second 第n與第n+1個數(shù)的和/// @return 返回斐波那契數(shù)列值 public int Fib5(int n, int first, int second) {if (n <= 1) {return first;} else {return fib5(n-1,second,first+second);} },也都是通過兩個變量保存計算值,傳遞給下一次進(jìn)行計算,遞歸的過程中也是根據(jù)n值變化逐步重復(fù)運算,和循環(huán)差不多,時間復(fù)雜度和空間復(fù)雜度也都一樣,優(yōu)雅了很多。
我們知道遞歸調(diào)用是通過棧來實現(xiàn)的,每調(diào)用一次函數(shù),系統(tǒng)都將函數(shù)當(dāng)前的變量、返回地址等信息保存為一個棧幀壓入到棧中,那么一旦要處理的運算很大或者數(shù)據(jù)很多,有可能會導(dǎo)致很多函數(shù)調(diào)用或者很大的棧幀,這樣不斷的壓棧,很容易導(dǎo)致棧的溢出。
還有一種優(yōu)化思路就是矩陣快速冪法,可參考鏈接 https://blog.csdn.net/computer_user/article/details/86927209
回復(fù)?【關(guān)閉】學(xué)關(guān)閉微信朋友圈廣告
回復(fù)?【實戰(zhàn)】獲取20套實戰(zhàn)源碼
回復(fù)?【福利】獲取最新微信支付有獎勵
回復(fù)?【被刪】學(xué)查看你哪個好友刪除了你巧
回復(fù)?【訪客】學(xué)微信查看朋友圈訪客記錄
回復(fù)?【卡通】學(xué)制作微信卡通頭像
回復(fù)?【python】學(xué)微獲取全套0基礎(chǔ)Python知識手冊
回復(fù)?【2019】獲取2019 .NET 開發(fā)者峰會資料PPT
winform已死?這8個Winform開源項目還有多少人在用?
VS Code 1.47 發(fā)布!官方版 Settings Sync 終于來了!
張善友:最新.NET程序員省份分布排名
Pandownload關(guān)閉了,百度網(wǎng)盤真的提速高達(dá)10Mb/s?
總結(jié)
以上是生活随笔為你收集整理的递归优化的这三种方式你知道吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在.NET Core中使用MongoDB
- 下一篇: 初识ABP vNext(5):ABP扩展