一直以來對於遞歸只是了解使用,最近在看javascript相關方面的書籍,看到用記憶功能優化遞歸,第一反應就是C#完全也可以實現,隨即便測試了一下遞歸的各種方式。
首先先來看一下javascript的記憶遞歸:
var fibonacci = function () { var memo = [0, 1]; var fib = function (n) { var result = memo[n]; if (typeof result !== 'number') { result = fib(n - 1) + fib(n - 2); memo[n] = result; } return result; }; return fib; }();
我們在一個名為memo的數組里保存我們的儲存結果,儲存結果可以隱藏之閉包中,當函數被調用是,這個函數首先檢查結果是否已存在,如果已經存在,就立即返回這個結果。
正文:C#的遞歸和非遞歸
先來一個大家非常熟悉的普通遞歸:
static int Fibonacci(int n) { return n < 2 ? n : Fibonacci(n - 1) + Fibonacci(n - 2); }
這個遞歸大家一目了然我就不做過多的解釋了,我們來看一下測試結果
現在來看一個我在網上找到的一個非遞歸模式:
static int Fibonacci(int n) {if (n <= 0) { return n; } int a = 1; int b = 1; for (int i = 3; i <= n; i++) { b = (a + b); a = b - a; } return b; }
由於遞歸的時間復雜度和空間復雜度較大,而且函數調用本身也會增加性能的開銷,所以每多遞歸一次,所損耗的時間成本近乎成倍增加,而非遞歸模式內部做了循環調用,減少了遞歸調用的次數,測試結果
再來一個我根據javascript的記憶功能做的一個C#遞歸:
//聲明一個記錄遞歸值的集合 static List<int> array = new List<int>() { 0, 1 }; static int Fibonacci(int n) { int result = 0; if (array.Count <= n) { result = Fibonacci(n - 1) + Fibonacci(n - 2); array.Add(result); } else { result = array[n]; } return result; }
直接來看測試結果:
我們將每次遞歸的結果存到集合中,下次遞歸前先查詢集合中是否當前遞歸值的結果,如果有直接返回,減少了遞歸調用的次數,時間的損耗也大大減少了。
還有一種遞歸方式把遞歸的的損耗也優化的非常不錯,我們來看一下尾遞歸:
static int Fibonacci(int n, int ret1, int ret2) { if (n == 0) return ret1; return Fibonacci(n - 1, ret2, ret1 + ret2); }
第一個參數是遞歸次數,第二和第三個參數的遞歸開始的基礎值,例如當前函數的調用方式:Fibonacci(10,0,1).我們來看一下測試結果:
通過對比我們發現尾遞歸運行的時間更短,效率更高,在實際應用中我們可以根據自己的需求去選擇不同的方式實現,但是在此建議如果可以用非遞歸實現盡量不要用遞歸,遞歸可能會造成堆棧溢出,但是也並非否定遞歸,最終的決定權還是在開發者手中。
本文以上代碼部分來自網絡,如有侵權,請聯系本人。
本文略顯簡陋,只用是用於博主記憶,若有錯誤,請指出,會做即使修改,謝謝。