博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【转】递归与优化:尾递归
阅读量:5146 次
发布时间:2019-06-13

本文共 2477 字,大约阅读时间需要 8 分钟。

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

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

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

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

  

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

 

参考资料: 

转载请标注来源:

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

你可能感兴趣的文章
ExtJS 4.2 业务开发(一)主页搭建
查看>>
webpack Import 动态文件
查看>>
电脑没有安装iis,但是安装了.NET环境,如何调试网站发布的程序
查看>>
【Mac + GitHub】之在另一台Mac电脑上下载GitHub的SSH链接报错
查看>>
Day03:Selenium,BeautifulSoup4
查看>>
Java NIO系列教程(九) ServerSocketChannel
查看>>
awk变量
查看>>
mysql_对于DQL 的简单举例
查看>>
postgis几何操作函数集
查看>>
ACM题目————还是畅通工程
查看>>
CentOS7使用firewalld打开关闭防火墙与端口
查看>>
35. Search Insert Position(C++)
查看>>
ubuntu 卡在登陆界面无法进入桌面,但是可以进入命令行界面
查看>>
【转】vim中多标签和多窗口的使用
查看>>
[毕业生的商业软件开发之路]C#异常处理
查看>>
chrome 禁止自动更新
查看>>
一些php文件函数
查看>>
std::min error C2059: 语法错误:“::” 的解决方法
查看>>
Opencv保存摄像头视频&&各种编码器下视频文件占用空间对比
查看>>
「图形学」直线扫描——Bresenham算法改进了中点Bresenham算法?
查看>>