【转】递归与优化:尾递归

落日映苍穹つ 2021-12-05 22:05 388阅读 0赞

了解尾递归之前,先了解一下尾调用。

在计算机科学里,尾调用是指一个函数里的最后一个动作是一个函数调用的情形:即这个调用的返回值直接被当前函数返回的情形。这种情形下该调用位置为尾位置。(摘自维基百科)

以上的解释来自维基百科。介绍了什么叫尾调用。例如:










1


2


3


4



function foo(data) {


    
a(data);


    
return
b(data);


}

  

这里的a(data)和b(data)都是函数调用,但是b(data)是函数返回前的最后运行的东西,所以也是所谓的尾位置。例如:










1


2


3


4


5


6


7


8


9


10


11



function foo1(data) {


    
return
a(data) + 1;


}


function foo2(data) {


    
var
ret = a(data);


    
return
ret;


}


function foo3(data) {


    
var
ret = a(data);


    
return
(ret === 0) ? 1 : ret;


}

  

这种就不是尾调用,对于foo1,最后一个动作是+1操作,并非是直接函数调用;对于foo3,是经过计算返回的结果,也不是尾调用。,foo2也不是尾调用

尾调用很重要的特性就是它可以不在调用栈上面添加一个新的堆栈帧,而是更新它。

接下来说一下什么是尾递归:

若一个函数在尾位置调用本身(或是一个尾调用本身的其他函数等),则称这种情况为尾递归,是递归的一种特殊情形。而形式上只要是最后一个return语句返回的是一个完整函数,它就是尾递归。这里注意:尾调用不一定是递归调用,但是尾递归一定是尾调用。

接下来通过斐波那契数列和阶乘来进一步理解尾递归的含义。

斐波那契数列

常规的斐波那契数列算法可能是这样的:










1


2


3


4


5


6


7



int
fib(
int
n) {


 


    
if
(n <= 2) {


        
return
1;


    
}


    
return
fib(n - 1) + fib(n - 2);


}

  

上面的这种递归计算最终的return操作是加法操作。所以不是尾递归。

如果用尾递归就是这样的:










1


2


3


4


5


6


7


8


9


10


11


12


13


14


15



/*


 
计算第n位斐波那契数列的值


  


 
@param n 第n个数


 
@param acc1 第n个数


 
@param acc2 第n与第n+1个数的和


 
@return 返回斐波那契数列值


 
/


int
tailfib(
int
n,
int
acc1,
int
acc2) {


    
if
(n < 2) {


        
return
acc1;


    
}


     


    
return
tailfib(n-1,acc2,acc1 + acc2);


}

  

比如我们想计算第10位斐波那契数列的值,只需要fib(10,1,1)即可。

看一下测试效果,测试程序如下:










1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17



int
main(
int
argc,
const
char
* argv[]) {


    
clock_t start,finish;


    


    
start = clock();


    
printf(
“计算结果:%d\n”
, fib(45));


    
finish = clock();


    
printf(
“花费时间————%lu\n”
,finish - start);


 


     


    
start = clock();


    
printf(
“计算结果:%d\n”
, tailfib(45,1,1));


    
finish = clock();


     


    
printf(
“花费时间————%lu\n”
,finish - start);


    
return
0;


     


}

  

计算结果如下:










1


2


3


4


5



计算结果:1134903170


花费时间————5540692


计算结果:1134903170


花费时间————4


Program ended with exit code: 0

  

效率可想而知。

阶乘

常规的计算阶乘的方法可能是这样的:










1


2


3


4


5


6



int
fac(
int
n) {


    
if
(n == 1) {


        
return
1;


    
}


    
return
fac(n-1) * n;


}

复杂度为O(n)

尾递归的算法是这样的:










1


2


3


4


5


6



int
tailfac(
int
n,
int
sum) {


    
if
(n == 1) {


        
return
sum;


    
}


    
return
fac(n-1, n * sum);


}

复杂度为O(1)

测试一下效率,测试程序如下:










1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17



int
main(
int
argc,
const
char
* argv[]) {


    
clock_t start,finish;


    


    
start = clock();


    
printf(
“计算结果:%d\n”
, fac(16));


    
finish = clock();


    
printf(
“花费时间————%lu\n”
,finish - start);


 


     


    
start = clock();


    
printf(
“计算结果:%d\n”
, tailfac(16,1));


    
finish = clock();


     


    
printf(
“花费时间————%lu\n”
,finish - start);


    
return
0;


     


}

  

测试结果:










1


2


3


4



计算结果:2004189184


花费时间————31


计算结果:2004189184


花费时间————2

  

尾递归效率比较高,但是个人觉得有尾递归算法理解起来会比较困难,你需要标注一下每个传入参数的作用,否则刚接触不一定会用这个算法。

参考资料:阮一峰的网络日志 维基百科

转载请标注来源:http://www.cnblogs.com/zhanggui/p/7722541.html

转载于:https://www.cnblogs.com/schips/p/11013899.html

发表评论

表情:
评论列表 (有 0 条评论,388人围观)

还没有评论,来说两句吧...

相关阅读

    相关

    尾递归 如果要说尾递归的话,那么首先应该先说一下递归函数。递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但是循环的逻辑不如递归清晰易理解

    相关 Kotlin优化

    一、尾递归优化 1.递归的一种特殊形式 2.调用自身后无其他的操作 3.tailrec关键字提示编译器尾递归优化 二、具体的来看看一下代码说明 pack

    相关 python

    递归函数可以方便的处理一些事物,但普通的递归是栈的堆积,如果堆积的过多就占用过多的内存资源,形象的一些递归就是就像是塔一样,从下至上层层叠加,直到,到达python的限制抛出异

    相关

    什么是尾递归 什么是尾递归呢?(tail recursion), 顾名思议,就是一种“不一样的”递归,说到它的不一样,就得先说说一般的递归。对于一般的递归,比如下面

    相关

    尾递归就是说一个递归函数,在return语句中调用了这个递归函数本身,如图所示。从理论上来说,尾递归都可以用非递归的方法实现。 ![这里写图片描述][70] [70]

    相关 09 优化

    尾递归,顾名思义,就是递归中调用自身的部分在函数体的最后一句。我们知道,递归调用对于栈大小的考验是非常大的,也经常会因为这个导致 StackOverflow,所以尾递归优化也是

    相关

    1、递归 简单的来说递归就是一个函数直接或间接地调用自身,是为直接或间接递归。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满