递归与尾递归

在最近作项目的时候碰到某个统计查询比较慢,比较影响效率,通过排查发现是这个程序使用了递归调用,我遍历的是组织架构,数据库

当这个层级很深时这个程序就会越慢,架构

这是我写的统计某个组织的下属组织总数函数

 1    /**
 2      * 叠加父级下子级的数量(递归调用)
 3      * @param pd 封装的PageData用来接受参数(能够理解成Map)
 4      * @param res  初始为0
 5      * @return
 6      * @throws Exception
 7      */
 8     public Integer listsubType(PageData pd,int res) throws Exception{
 9         int count=this.subType(pd);//统计该组织数量(能够是人员或者类型数量)
10         res+=count;//叠加每次的数量
11         List<PageData> list=listParentId(pd);//遍历该组织下属组织架构
12         for(PageData pd2:list){//遍历若是无则跳出
13            this.listsubType(pd2,res);//继续调用
14         }
15         return res;//返回统计结果
16     }

这个是我本地的数据库,数据量并很少测试

普通递归执行花费时间优化

怎么优化一开始我并无思路,可是经过百度我逐渐了解递归的效率可谓是声名狼藉,那么效率与代码简洁是否能够共存呢?答案是确定的this

尾递归  百度百科的解释为:若是一个函数中全部递归形式的调用都出如今函数的末尾,咱们称这个递归函数是尾递归的spa

让函数在末尾被调用,好办, 因而稍微改了下代码(其实就是把this改成了return)
code

 1 /**
 2      * 叠加父级下子级的数量(递归调用)
 3      * @param pd 封装的PageData用来接受参数(能够理解成Map)
 4      * @param res  初始为0
 5      * @return
 6      * @throws Exception
 7      */
 8     public Integer listsubType(PageData pd,int res) throws Exception{
 9         int count=this.subType(pd);//统计数量
10         res+=count;//叠加每次的数量
11         List<PageData> list=listParentId(pd);//遍历该组织下属组织架构
12         for(PageData pd2:list){//遍历若是无则跳出
13            return listsubType(pd2,res);//继续调用
14         }
15         return res;//返回统计结果
16     }

尾递归执行花费时间blog

 

差距是否是很明显,固然我取得只是测试中某一个值,二者之间花费时间我也各测试了好几回,我取的是大概中间值递归

其实到了这一步也差很少完成了优化,可我对这其中的原理仍是只知其一;不知其二,普通递归好理解,但尾递归呢?因而继续研究

 1 // 例一
 2 function f(x){
 3   let y = f(x);
 4   return y;
 5 }
 6 
 7 // 例二
 8 function f(x){
 9   return f(x) + 1;
10 }

上面的的两个都不属于尾递归(不对,应该是不属于尾调用),由于在调用自身后它们还须要进行操做,

尾调用:尾调用是指一个函数里的最后一个动做是一个函数调用的情形,即这个调用的返回值直接被当前函数返回的情形。这种情形下称该调用位置称为“尾位置”

尾递归:若一个函数在尾位置调用自身,则称这种状况为尾递归。尾递归是递归的一种特殊情形

这样也算是初步理解了尾递归与尾调用的概念了

那么为何普通递归与尾递归的查询效率会差这么多呢

查询资料得:

函数调用会在内存造成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。若是在函数A的内部调用函数B,那么在A的调用记录上方,还会造成一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。若是函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。全部的调用记录,就造成一个"调用栈"(call stack)

尾调用因为是函数的最后一步操做,因此不须要保留外层函数的调用记录,由于调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用记录,取代外层函数的调用记录就能够了

本身理解的以下

这是用普通递归求1到n的和

 

 1 int fn( int n )
 2 {
 3     int result;
 4     if(n<=0)
 5     result=0;
 6     else if(n==1)
 7     result=1;
 8     else
 9     result=fn(n-1)+n;
10     return result;
11 }

 

若是输入的是3,那么你们理解的就是2+3=5,里面的真实计算为fn(0)+fn(1)+fn(2)+fn(3)

这是用尾递归求1到n的和

1 int fn( int n,int res)
2     {
3         if(n<=0)
4            return res=0;
5         else if(n==1)
6            return res;
7 
8         return fn(n-1,n+res);
9     }

若是输入的是3,那么就是2+3=5等价于fn(2)+fn(3),由于2和3已经计算好的能够直接拿过来用,而不须要再次计算

不用尾递归,函数的堆栈耗用难以估量,须要保存不少中间函数的堆栈。好比f(n, sum) = f(n-1) + value(n) + sum; 会保存n个函数调用堆栈,而使用尾递归f(n, sum) = f(n-1, sum+value(n)); 这样则只保留后一个函数堆栈便可

以上我写的优化查询的确实有问题,但递归与尾递归的解释仍是能够参考的

相关文章
相关标签/搜索