从斐波那契开始了解尾递归

尾递归(tail recursive),看名字就知道是某种形式的递归。简单的说递归就是函数自己调用自己。那尾递归和递归之间的差别就只能体现在参数上了。

尾递归wiki解释如下:豪享博娱乐城

尾部递归是一种编程技巧。递归函数是指一些会在函数内调用自己的函数,如果在递归函数中,递归调用返回的结果总被直接返回,则称为尾部递归。尾部递归的函数有助将算法转化成函数编程语言,而且从编译器角度来说,亦容易优化成为普通循环。这是因为从电脑的基本面来说,所有的循环都是利用重复移跳到代码的开头来实现的。如果有尾部归递,就只需要叠套一个堆栈,因为电脑只需要将函数的参数改变再重新调用一次。利用尾部递归最主要的目的是要优化,例如在Scheme语言中,明确规定必须针对尾部递归作优化。可见尾部递归的作用,是非常依赖于具体实现的。

我们还是从简单的斐波那契开始了解尾递归吧。

用普通的递归计算Fibonacci数列:

01 #include "stdio.h"
02 #include "math.h"
03  
04 int factorial(int n);
05  
06 int main(void)
07 {
08     int i, n, rs;
09  
10     printf("请输入斐波那契数n:");
11     scanf("%d",&n);
12  
13     rs = factorial(n);
14     printf("%d \n", rs);
15  
16     return 0;
17 }
18  
19 // 递归
20 int factorial(int n)
21 {
22     if(n <= 2)
23     {
24         return 1;
25     }
26     else
27     {
28         return factorial(n-1) + factorial(n-2);
29     }
30 }

程序员运行结果如下:

1 请输入斐波那契数n:20
2 6765
3  
4 Process returned 0 (0x0)   execution time : 3.502 s
5 Press any key to continue.

在i5的CPU下也要花费 3.502 秒的时间。

下面我们看看如何用尾递归实现斐波那契数。

01 #include "stdio.h"
02 #include "math.h"
03  
04 int factorial(int n);
05  
06 int main(void)
07 {
08     int i, n, rs;
09  
10     printf("请输入斐波那契数n:");
11     scanf("%d",&n);
12  
13     rs = factorial_tail(n, 1, 1);
14     printf("%d ", rs);
15  
16     return 0;
17 }
18  
19 int factorial_tail(int n,int acc1,int acc2)
20 {
21     if (n < 2)
22     {
23         return acc1;
24     }
25     else
26     {
27         return factorial_tail(n-1,acc2,acc1+acc2);
28     }
29 }

程序员运行结果如下:

1 请输入斐波那契数n:20
2 6765
3 Process returned 0 (0x0)   execution time : 1.460 s
4 Press any key to continue.

快了一倍有多。当然这是不完全统计,有兴趣的话可以自行计算大规模的值,这里只是介绍尾递归而已。

我们可以打印一下程序的执行过程,函数加入下面的打印语句:

01 int factorial_tail(int n,int acc1,int acc2)
02 {
03     if (n < 2)
04     {
05         return acc1;
06     }
07     else
08     {
09         printf("factorial_tail(%d, %d, %d) \n",n-1,acc2,acc1+acc2);
10         return factorial_tail(n-1,acc2,acc1+acc2);
11     }
12 }

程序运行结果:

01 请输入斐波那契数n:10
02 factorial_tail(9, 1, 2)
03 factorial_tail(8, 2, 3)
04 factorial_tail(7, 3, 5)
05 factorial_tail(6, 5, 8)
06 factorial_tail(5, 8, 13)
07 factorial_tail(4, 13, 21)
08 factorial_tail(3, 21, 34)
09 factorial_tail(2, 34, 55)
10 factorial_tail(1, 55, 89)
11 55
12 Process returned 0 (0x0)   execution time : 1.393 s
13 Press any key to continue.

从上面的调试就可以很清晰地看出尾递归的计算过程了。acc1就是第n个数,而acc2就是第n与第n+1个数的和,这就是我们前面讲到的“迭代”的精髓,计算结果参与到下一次的计算,从而减少很多重复计算量。

fibonacci(n-1,acc2,acc1+acc2)真是神来之笔,原本朴素的递归产生的栈的层次像二叉树一样,以指数级增长,但是现在栈的层次却像是数组,变成线性增长了,实在是奇妙,总结起来也很简单,原本栈是先扩展开,然后边收拢边计算结果,现在却变成在调用自身的同时通过参数来计算。

小结

尾递归的本质是:将单次计算的结果缓存起来,传递给下次调用,相当于自动累积。

在Java等命令式语言中,尾递归使用非常少见,因为我们可以直接用循环解决。而在函数式语言中,尾递归却是一种神器,要实现循环就靠它了。

很多人可能会有疑问,为什么尾递归也是递归,却不会造成栈溢出呢?因为编译器通常都会对尾递归进行优化。编译器会发现根本没有必要存储栈信息了,因而会在函数尾直接清空相关的栈。

时间: 2024-10-13 19:26:26

从斐波那契开始了解尾递归的相关文章

Python实现斐波那契递归和尾递归计算

##斐波那契递归测试 def fibonacciRecursive(deepth): if deepth == 1: return 1 elif deepth == 2: return 1 else: return fibonacciRecursive(deepth - 1) + fibonacciRecursive(deepth - 2) ##斐波那契尾递归测试 def fibonacciTailRecursive(num, ret1, rte2): if num == 1: return r

斐波那契数列 递归 尾递归 递推 C++实现

==================================声明================================== 本文原创,转载请注明作者和出处,并保证文章的完整性(包括本声明). 本文不定期修改完善,为保证内容正确,建议移步原文处阅读. 本文链接:http://www.cnblogs.com/wlsandwho/p/4205524.html ===============================================================

递归和尾递归的比较,斐波那契

相信如果一个人让我们求一个斐波那契数列,如果你学过c语言,你一定会说用递归法啊,很容易就实现了,但是如果人家让你求斐波那契的第50个数,而且你对递归了解的话,估计帮你不会说递归了,如果了解够深的话,其实你会说递归也可以求出来. 1.递归 首先我们来说说什么是递归,简单的来说,就是一个函数需要调用自己来完成某种功能,这种调用就叫做递归. 但我们需要清楚一点,递归在使用的时候,并不是一直调用自己,我们需要给他一个停下来的时机.就像打仗一样,要知道进攻的路线,但如果遇到突发状况也要能及时撤退.所以我们

尾递归实现斐波那契数列

一.斐波那契数列 斐波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368...... 二.递归算法 1. 代码 public int fib(int n){ if(n==1 || n==2){ return 1; } return fib(n-1)+fib(n-2); } 2. 缺点:多次计算重复的fib(n),性能

递归算法——求取斐波那契数列(2)

import java.util.Scanner; /** * Created by Administrator on 14-5-13. * 改进的计算斐波那契数列的方法,利用参数,经过测试运行时间会成倍减少 测试数据n=40 * 尾递归的本质是:将单次计算的结果缓存起来,传递给下次调用,相当于自动累积. * 尾部递归是一种编程技巧.递归函数是指一些会在函数内调用自己的函数, * 如果在递归函数中,递归调用返回的结果总被直接返回,则称为尾部递归. * 尾部递归的函数有助将算法转化成函数编程语言,

斐波那契数列深入学习

问题定义: 具体文字定义就不多说了,网上有很多,下面给出数学公式描述(有时候会有一些变种,不过都大同小异): f(0) = 1 , f(1) = 1, f(n) = f(n-1)+f(n-2) n>=2 1.递归求解 解决斐波那契问题,大多数人第一反应就是递归,思路简单清晰,代码易实现,不多说了,直接看代码. 1 long long int Fibonacci(int n) 2 { 3 if (n < 2) { 4 return 1; 5 } 6 7 return Fibonacci(n-1)

面试官问你斐波那契数列的时候不要高兴得太早

前言 假如面试官让你编写求斐波那契数列的代码时,是不是心中暗喜?不就是递归么,早就会了.如果真这么想,那就危险了. 递归求斐波那契数列 递归,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法.斐波那契数列的计算表达式很简单: 1F(n) = n; n = 0,12F(n) = F(n-1) + F(n-2),n >= 2; 因此,我们能很快根据表达式写出递归版的代码: 1/*fibo.c*/ 2#include <stdio.h> 3#include <stdlib.h&

求斐波那契数列第n位的几种实现方式及性能对比(c#语言)

在每一种编程语言里,斐波那契数列的计算方式都是一个经典的话题.它可能有很多种计算方式,例如:递归.迭代.数学公式.哪种算法最容易理解,哪种算法是性能最好的呢? 这里给大家分享一下我对它的研究和总结:下面是几种常见的代码实现方式,以及各自的优缺点.性能对比. Iteration using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; public class Progr

斐波那契函数的优化

Android开发者使用java开发,但是Android平台并没有使用java虚拟机来执行代码,而是把代码编译成Android使用的虚拟机的字节码(Dalvik 虚拟机).java代码先是被编译成了java的字节码,然后会被odex 编译器编译成delvik虚拟机执行的字节码.无论是Android中还是java中程序的性能优化都是必不可少的一大难题.我们先从简单的斐波那契数列来简做分析: 1.什么是斐波那契数列? a)?例如 0 ?1 ?2 ?3 ?5 ?8 ?13 ..... 这样的数列.第一