生活中,如果1+2+3+4.....+100,大家基本上都会用等差数列计算,如果有人从1开始加,不是傻就是白X,那么程序中呢,是不是也是这样。今天无意中看到了尾递归,以前也写过,但是不知道这个专业名词,今天写一下对比下性能问题。

今天主要是看到了尾递归,所以联想到了这些,写下这篇文章,其中也把Benchmark (Nuget上的BenchmarkDotNet)的基准测试用了下,感觉比较好用,赞。Benchmark 需要在release下运行。

原则上所有的递归操作,都可以写成循环操作。尾递归是指,在函数返回的时候,调用自身本身,并且return语句不能包含表达式。这样编译器或者解释器就可以把尾递归做优化,试运行速度更快。

测试类

public class TestClass
{
/// <summary>
/// for循环
/// </summary>
/// <param name="n"></param>
/// <param name="counter"></param>
/// <returns></returns>
public int TestFor(int n)
{
int s = ; for (int i = ; i < n + ; i++)
{
s = s + i;
} return s;
} /// <summary>
/// 等差数列
/// </summary>
/// <param name="n"></param>
/// <param name="counter"></param>
/// <returns></returns>
public int TestForG(int n)
{
int s = ( + n) * n / ; return s;
} /// <summary>
/// 递归
/// </summary>
/// <param name="n"></param>
/// <param name="counter"></param>
/// <returns></returns>
public int TestRec(int n)
{
if (n == ) return ;
return n + TestRec(n - );
} /// <summary>
/// 尾递归
/// </summary>
/// <param name="n"></param>
/// <param name="counter"></param>
/// <returns></returns>
public int TestTail(int n)
{
return TestTail(, n);
} public int TestTail(int sum, int n)
{
if (n == ) return sum;
sum = sum + n;
return TestTail(sum, n - );
}
}

基准测试

[SimpleJob(RuntimeMoniker.NetCoreApp30)]
[RPlotExporter]
public class TestClassForBenchmark
{
private readonly TestClass testClass = new TestClass(); [Params(,,,)]
public int N; [Benchmark]
public void TestFor()
{
testClass.TestFor(N);
} [Benchmark]
public void TestForG()
{
testClass.TestForG(N);
} [Benchmark]
public void TestRec()
{
testClass.TestRec(N);
} [Benchmark]
public void TestTail()
{
testClass.TestTail(N);
} }

Main程序调用

BenchmarkRunner.Run<TestClassForBenchmark>();

结果

用Benchmark的基准测试发现,运行时间:等差 < for < 尾递归(接近for) < 递归,for的运行速度比递归快,但是递归结构比较清晰,容易理解。

发现TestForG有点问题,接下来自己简单测试

实际用Stopwatch测试

TestClass testClass = new TestClass();

Stopwatch stopSwitch = new Stopwatch();
int n = ;
stopSwitch.Start();
int sum = testClass.TestFor(n);
stopSwitch.Stop();
Console.WriteLine($"结果:{sum},TestFor时间:{stopSwitch.ElapsedTicks}"); stopSwitch.Start();
sum = testClass.TestForG(n);
stopSwitch.Stop();
Console.WriteLine($"结果:{sum},TestForG时间:{stopSwitch.ElapsedTicks}"); stopSwitch.Restart();
sum = testClass.TestRec(n);
stopSwitch.Stop();
Console.WriteLine($"结果:{sum},TestRec时间:{stopSwitch.ElapsedTicks}"); stopSwitch.Restart();
sum = testClass.TestTail(n);
stopSwitch.Stop();
Console.WriteLine($"结果:{sum},TestTail时间:{stopSwitch.ElapsedTicks}");

Stopwatch测试结果

. 10次
结果:,TestFor时间:
结果:,TestForG时间:
结果:,TestRec时间:
结果:,TestTail时间: .
结果:,TestFor时间:
结果:,TestForG时间:
结果:,TestRec时间:
结果:,TestTail时间:
.
结果:,TestFor时间:
结果:,TestForG时间:
结果:,TestRec时间:
结果:,TestTail时间:
.
结果:,TestFor时间:
结果:,TestForG时间:
结果:,TestRec时间:
结果:,TestTail时间:
.
结果:,TestFor时间:
结果:,TestForG时间:
结果:,TestRec时间:
结果:,TestTail时间:

结论

1. for的运行速度比递归快,但是递归结构比较清晰,容易理解。

2. 等差计算不一定比for循环快

斐波那契数列对比

        /// <summary>
/// 循环实现 counter:运行次数
/// </summary>
public long Fib(int n, ref int counter)
{
if (n < ) return ;
long a = , b = ;
long temp;
for (int i = ; i <= n; i++)
{
counter++;
temp = a;
a = b;
b = temp + b;
} return b;
} /// <summary>
/// 递归实现
/// </summary>
public long FibRec(int n, ref int counter)
{
counter++;
if (n < ) return ;
if (n < ) return ;
return FibRec(n - , ref counter) + FibRec(n - , ref counter);
} /// <summary>
/// 尾递归实现
/// </summary>
public long FibTailRec(int n, ref int counter)
{
if (n < ) return ;
if (n < ) return ;
return FibRec(, , n, ref counter);
} public long FibRec(long last, long prev, int n, ref int counter)
{
counter++; long temp = last + prev;
if (n == ) return temp;
last = prev;
prev = temp; return FibRec(last, prev, n - , ref counter);
}

效果

counter是运行的次数,斐波那契数列直接用递归,n=100都直接堆栈溢出。递归用的好了,思路清晰,用的不好的话,数据稍微大点就是深深的坑。用递归尽量优化为尾递归,也就是返回的时候调用自身,不要有表达式。

等差数列,for循环,递归和尾递归的对比的更多相关文章

  1. 递归与尾递归(C语言)

    原文:递归与尾递归(C语言)[转] 作者:archimedes 出处:http://www.cnblogs.com/archimedes/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留 ...

  2. ArrayList和LinkedList的几种循环遍历方式及性能对比分析(转)

    主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayList和LinkedList的源码实现分析性能结果,总结结论. 通过本文你可以 ...

  3. ArrayList和LinkedList的几种循环遍历方式及性能对比分析

    最新最准确内容建议直接访问原文:ArrayList和LinkedList的几种循环遍历方式及性能对比分析 主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性 ...

  4. ArrayList和LinkedList的几种循环遍历方式及性能对比分析(转载)

    原文地址: http://www.trinea.cn/android/arraylist-linkedlist-loop-performance/ 原文地址: http://www.trinea.cn ...

  5. HashMap循环遍历方式及其性能对比(zhuan)

    http://www.trinea.cn/android/hashmap-loop-performance/ ********************************************* ...

  6. HashMap循环遍历方式及其性能对比

    主要介绍HashMap的四种循环遍历方式,各种方式的性能测试对比,根据HashMap的源码实现分析性能结果,总结结论.   1. Map的四种遍历方式 下面只是简单介绍各种遍历示例(以HashMap为 ...

  7. 【转】ArrayList和LinkedList的几种循环遍历方式及性能对比分析

    原文网址:http://www.trinea.cn/android/arraylist-linkedlist-loop-performance/ 主要介绍ArrayList和LinkedList这两种 ...

  8. linux循环递归设置权限

    这里给出一个循环递归得到对文件夹和文件分别有效的设置方法: find /path -type f -exec chmod 644 {} \; #对目录和子目录里的文件 find /path -type ...

  9. Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 [ 转载 ]

    Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 @author Trinea 原文链接:http://www.trinea.cn/android/arrayl ...

随机推荐

  1. JQuery--50个必备的实用jQuery代码段.

    原文出处:http://my.oschina.net/chengjiansunboy/blog/55496?p=2#comments 1. 如何修改jQuery默认编码(例如默认UTF-8改成改GB2 ...

  2. 三、Django学习之单表查询接口

    查询接口 all() 查询所有结果,结果是queryset类型 filter(**kwargs) and条件关系:参数用逗号分割表示and关系 models.Student.objects.filte ...

  3. phpstrom激活码

    今天PHPstorm又到期了,从网上找到一个激活码的网址,很好用,说是会时时更新的,所以特意记录一下 获取地址:https://www.php.cn/tool/phpstorm/408348.html

  4. python day02练习和作业

    # 1.请用代码实现:利用下划线将列表的每一个元素拼接成字符串,li=[‘alex’, ‘eric’, ‘rain’]# li=['alex','eric','rain']# print('_'.jo ...

  5. vcs/verdi filist

    . 现在很多verilog 的filelist 文件中用如下形式来指定文件所在的目录就行了,而不必指定具体文件 ./testbench/ppc460_PLB4/p464s_test_top_tb.v ...

  6. 题解 USACO12DEC【逃跑的BarnRunning Away From…】

    期末考前写题解,\(rp++! \ rp++! \ rp++!\) \[ description \] 给出一个以 \(1\) 为根的边带权有根树,给定一个参数 \(L\) ,问每个点的子树中与它距离 ...

  7. mybaitis的延迟加载

    概念:延迟加载:用到的时候才加载 因为我们在多表查询是,效率不如单表快,多个单表查询,然后使用懒加载,完成 多表关联查询 什么情况下使用懒加载 mybaitis中的表关系是一对一或者一对多的时候 我们 ...

  8. 阿里云服务器ECS Ubuntu18.04 初次使用配置教程(图形界面安装)

    最近由于工作需要,要使用服务器测试,就先自已买了个服务器,就在阿里云买了一个,先买了那个叫虚拟主机的,后来发现不是我需要的,所以退了,就先了这个ECS主机.3年.如果购买就上图了.下面直接进入正题. ...

  9. ELF文件之四——使用链接脚本-2个函数-data

    main.c ; int main() { ; } int add() { ; } main.o 反汇编可以看到多了.text节的反汇编,存储的是全局变量的初始化数值 main.o对比,text段后面 ...

  10. apache 访问状态 分析

    状态查看: 1.查看apache 各状态连接数 [root]#netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' ...