生活中,如果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. python学习Day04--列表

    [主要内容] 1.列表的索引: lst=[1,2,3,4,5,6] print(lst[0])      #获取第一个元素 lst[1]='你好'    #改变列表中的值 2.列表的切片: lst=[ ...

  2. 《快乐编程大本营》java语言训练班-第4课:java流程控制

    <快乐编程大本营>java语言训练班-第4课:java流程控制 第1节. 顺序执行语句 第2节. 条件分支语句:if条件语句 第3节. 条件分支语句:switch 条件语句 第4节. 条件 ...

  3. Spring配置 bean

    在 Spring 的 IOC 容器里配置 Bean <bean id="helloWorld" class="com.xiya.spring.beans.Hello ...

  4. C++不同类中的特征标相同的同名函数

    转载请注明出处,版权归作者所有 lyzaily@126.com yanzhong.lee        作者按:                   从这篇文章中,我们主要会认识到一下几点:      ...

  5. Goland2019.3.2永久破解

    2019.11.28 jetbrains公司发布了Go的最强编辑器GoLand 2019.3.本次更新软件消耗更少的CPU和更快的性能,增强了对Go Modules的支持,添加了一组新的快速修复程序, ...

  6. 【WPF学习】第四十二章 透明

    WPF支持真正的透明效果.这意味着,如果在一个性质或元素上层叠另外几个形状或元素,并让所有这些形状和元素具有不同的透明度,就会看到所期望的效果.通过该特性能够创建透过上面的元素可以看到的的图像背景,这 ...

  7. python中调用函数时,参数顺序与参数赋值问题

    设置类和函数如下:class MM(): def ff(self,url(1),method(2),data=None(3),cookie=None(4)): if method.lower()==& ...

  8. Java:枚举类也就这么回事

    目录 一.前言 二.源自一道面试题 三.枚举的由来 四.枚举的定义形式 五.Enum类里有啥? 1.唯一的构造器 2.重要的方法们 3.凭空出现的values()方法 六.反编译枚举类 七.枚举类实现 ...

  9. onboard procossor and cross-compile

    星载处理器 ERC32-TSC695F The European Space Agency’s ERC32 is a microprocessor implementing a SPARC V7 pr ...

  10. php页面传递参数值几种方法总结

    2013-06-06 18:02 (分类:) 又搞了一个学期的php,就这样吧. php是一种服务器的脚本语言,他也是现在最为流行的WEB开发语言,下面我们来讲述一下几种上在php开发应用中常用的四种 ...