程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#也玩尾遞歸(續)

C#也玩尾遞歸(續)

編輯:C#入門知識

 

今天重新看了下文中最後的實現代碼,感覺還是不夠滿意:因為引入了一個對用戶來說不是很必要的數據結構RecFunc<>,用戶需要定義的代碼大概是這樣:

 

(rec,i,n,a,b) => (n<3 ? 1 : (i==n ? a+b : rec.Callback(i+1, n, b, a+b)))

  從調用方的角度來看,下面代碼就比上面的更容易理解(不用self都不好意思說自己學過python):

 

(self,i,n,a,b) => (n<3 ? 1 : (i==n ? a+b : self(i+1, n, b, a+b)))

  是不是該將那個類繼承自Delegate?可是C#不許直接繼承這個特殊類。或許可以用Delegate.CreateDelegate創建一個委托對象來組合?順著這個方向,進而想到了直接創建一個匿名委托來實現。

 

  於是我將代碼重新修改了下,短小精悍了不少,最終尾遞歸包裝的代碼如下:

 

public static Func<T1,T2,T3,T4,TResult> TailRecursive<T1,T2,T3,T4,TResult>

    (Func<Func<T1,T2,T3,T4,TResult>,T1,T2,T3,T4,TResult> func)

{

    return (p1,p2,p3,p4) =>

    {

        bool callback = false;

        Func<T1,T2,T3,T4,TResult> self = (q1,q2,q3,q4) =>

        {

            p1 = q1;

            p2 = q2;

            p3 = q3;

            p4 = q4;

            callback = true;

            return default(TResult);

        };

        do

        {

            var result = func(self,p1,p2,p3,p4);

            if (!callback)

            {

                return result;

            }

            callback = false;

        } while (true);

    };

}

  另外還補充了普通遞歸的包裝方法:

 

public static Func<T1,T2,T3,T4,TResult> Recursive<T1,T2,T3,T4,TResult>

    (Func<Func<T1,T2,T3,T4,TResult>,T1,T2,T3,T4,TResult> func)

{

    Func<T1,T2,T3,T4,TResult> self = null;

    self = (p1,p2,p3,p4) => func(self,p1,p2,p3,p4);

    return self;

}

  寫完代碼,自然還是要測試下的,我測試對比了三種方式的計算Fib數列的性能:(1)最基本的遞歸調用的方式(2)用上面的Recursive<> (3)用上面的TailRecursive<>尾遞歸。

 

  前兩種都屬於普通遞歸,受到調用棧的限制Fib(10000)還可以執行,但是Fib(20000)就堆棧溢出了。第三種方法由於是尾遞歸,調用Fib(20000)自然沒問題,就是Fib(10000000)也是瞬間執行完。

 

  測試總要有數據支持才行,剛剛的測試發現,前兩種方法Fib(10000)時Stopwatch記錄的毫秒數都是0,無法根據花費時間來比較三者的性能。於是我又加了段很邪惡的測試代碼,白白浪費CPU時間:

 

// 外部初始化

int x = 10000;

var s = x.ToString();

// 遞歸循環中

x = -x;

for (int j = 0; j < Math.Abs(x); j++)

{

    s = (s + x.ToString()).Substring(0, 5);

}

  料想這代碼該不會被編譯器或JIT優化掉吧?果然,添加後時間對比就比較明顯了:

 

Normal

Fib(100) = -980107325 (206 ms)

Fib(1000) = 1556111435 (2419 ms)

Fib(10000) = 1242044891 (65435 ms)

/////////////////////////////////////

Recursive

Fib(100) = -980107325 (192 ms)

Fib(1000) = 1556111435 (2735 ms)

Fib(10000) = 1242044891 (103156 ms)

/////////////////////////////////////

TailRecursive

Fib(100) = -980107325 (186 ms)

Fib(1000) = 1556111435 (1889 ms)

Fib(10000) = 1242044891 (18969 ms)

  可以看出,在遞歸次數較多的情況下,尾遞歸確實在性能上有較大的優勢。出乎意料的是像Recursive這種簡單的包裝竟然降低了不少性能,不知有哪位大哥可以看看測試代碼,看到底問題出在哪裡?或許哪天我想通了,到時可以再寫篇<續2>呵呵

 

測試代碼

static void Main(string[] args){    Console.WriteLine("Normal");    int x = 10000;    var s = x.ToString();    GC.Collect();    GC.WaitForPendingFinalizers();    Func<int, int, int, int, int> fib1 = null;    fib1 = (i, n, a, b) =>    {        x = -x;        for (int j = 0; j < Math.Abs(x); j++)        {            s = (s + x.ToString()).Substring(0, 5);        }        return (n < 3 ? 1 : (i == n ? a + b : fib1(i + 1, n, b, a + b)));    };    Action<int> Fib1 = n =>    {        Console.Write("Fib({0}) = ", n);        var sw = System.Diagnostics.Stopwatch.StartNew();        var result = fib1(3, n, 1, 1);        sw.Stop();        Console.WriteLine("{0} ({1} ms)", result, sw.ElapsedMilliseconds);    };    Fib1(100);    Fib1(1000);    Fib1(10000);    Console.WriteLine("/////////////////////////////////////");    Console.WriteLine("Recursive");    GC.Collect();    GC.WaitForPendingFinalizers();    var fib2 = Recursive<int, int, int, int, int>((self, i, n, a, b) =>    {        x = -x;        for (int j = 0; j < Math.Abs(x); j++)        {            s = (s + x.ToString()).Substring(0, 3);        }        return (n < 3 ? 1 : (i == n ? a + b : self(i + 1, n, b, a + b)));    });    Action<int> Fib2 = n =>    {        Console.Write("Fib({0}) = ", n);        var sw = System.Diagnostics.Stopwatch.StartNew();        var result = fib2(3, n, 1, 1);        sw.Stop();        Console.WriteLine("{0} ({1} ms)", result, sw.ElapsedMilliseconds);    };    Fib2(100);    Fib2(1000);    Fib2(10000);    Console.WriteLine("/////////////////////////////////////");    Console.WriteLine("TailRecursive");    GC.Collect();    GC.WaitForPendingFinalizers();    var fib3 = TailRecursive<int, int, int, int, int>((self, i, n, a, b) =>    {        x = -x;        for (int j = 0; j < Math.Abs(x); j++)        {            s = (s + x.ToString()).Substring(0, 3);        }        return (n < 3 ? 1 : (i == n ? a + b : self(i + 1, n, b, a + b)));    });    Action<int> Fib3 = n =>    {        Console.Write("Fib({0}) = ", n);        var sw = System.Diagnostics.Stopwatch.StartNew();        var result = fib3(3, n, 1, 1);        sw.Stop();        Console.WriteLine("{0} ({1} ms)", result, sw.ElapsedMilliseconds);    };    Fib3(100);    Fib3(1000);    Fib3(10000);}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved