递归

递归深入理解

递归最重要的一点就是假设子问题已经解决了,现在要基于已经解决的子问题来解决当前问题。
递归之所以把问题变简单了,是因为相当于多给你了一个前提条件(尽管是假设的)。

大家都知道,递归的本质和栈数据的存取很相似了,都是先进去,但是往往最后处理!再者对于递归函数的局部变量的存储是按照栈的方式去存的,对于每一层的递归函数在栈中都保存了本层函数的局部变量,一边该层递归函数结束时能够保存原来该层的数据!如图:
这里写图片描述
如上图递归式依次往下进行的,并且在该层递归函数还没结束即将进入下一层递归调用时,将会把该层函数中的局部变量保存起来,以供下次使用!
好了,以上是递归函数的数据存储方式,可是有时候我们又得抓头了,递归的话,有时候又很难理解,貌似总也想不通!
于是我又把每一层递归函数化分为三部分了,第一部分:是递归调用前的一些数据处理,判断以及递归结束判断(当然了结束条件肯定在递归调用前,要不每次递归就不会结束了),第二部分:就是递归函数本身了。而第三部分:当然就是递归函数的后续处理代码了!在这里我想我们得想明白一件事情了,每一层的函数都是在上一层递归函数结束时才返回的然后接着处理该层递归函数剩下的部分!例如如下代码:

#include<iostream>  
#include<string>  
using namespace std;  

int i=0,j;  
void reverse(string &s);  
int main()  
{  
    string s;  

    cin>>s;  
    j=i=s.size();  
    reverse(s);  
    cout<<s<<endl;  

    return 0;  
}  

void reverse(string &s)  
{  
    char ch;                    //..........第一部分 ..........  
    i--;  
    ch=s[i];  
    cout<<ch<<endl;             //这里i是全局变量,而ch是局部变量会保存在栈中  
    if(-1==i)  
        return;                    
    reverse(s);                 //本身的递归看做第二部分  
                                //后续部分看做第三部分  
    s[--j]=ch;               //这句当且仅当该递归函数中的reverse返回时 才执行  
    cout<<ch<<endl;  
}  

在以上代码中,每一层只有当reverse()结束了才会接着处理下面的s[–j]=ch;代码,因为每一次递归进去的时候reverse()上面的代码都已经处理了,所以当递归返回时处理的自然就是reverse()下面的代码了,如此循环直到结束!不过我觉着最重要的还有一样就是有时候不必刻意去关注的那么细,也要有全局观,例如我们只需要知道函数reverse()是继续处理同样的功能,没必要再去想这个函数里面又是怎么样怎么样的,我感觉肯定会抓狂的!希望跟我一样纠结的朋友不在纠结递归了………

另外,
递归的使用条件:

  存在一个递归调用的终止条件;

  每次递归的调用必须越来越靠近这个条件;只有这样递归才会终止,否则是不能使用递归的!

总之,在你使用递归来处理问题之前必须首先考虑使用递归带来的好处是否能补偿

  他所带来的代价!否则,使用迭代算法会比递归算法要高效。

递归的基本原理:

  1 每一次函数调用都会有一次返回.当程序流执行到某一级递归的结尾处时,它会转移到前一级递归继续执行.

  2 递归函数中,位于递归调用前的语句和各级被调函数具有相同的顺序.如打印语句 #1 位于递归调用语句前,它按照递归调用的顺序被执行了 4 次.

  3 每一级的函数调用都有自己的局部变量.

  4 递归函数中,位于递归调用语句后的语句的执行顺序和各个被调用函数的顺序相反.

       即位于递归函数入口前的语句,右外往里执行;位于递归函数入口后面的语句,由里往外执行。

  5 虽然每一级递归有自己的变量,但是函数代码并不会得到复制.

  6 递归函数中必须包含可以终止递归调用的语句.

一旦你理解了递归(理解递归,关键是脑中有一幅代码的图片,函数执行到递归函数入口时,就扩充一段完全一样的代码,执行完扩充的代码并return后,继续执行前一次递归函数中递归函数入口后面的代码),阅读递归函数最容易的方法不是纠缠于它的执行过程,而是相信递归函数会顺利完成它的任务。如果你的每个步骤正确无误,你的限制条件设置正确,并且每次调用之后更接近限制条件,递归函数总是能正确的完成任务。

不算递归调用语句本身,到目前为止所执行的语句只是除法运算以及对quotient的值进行测试。由于递归调用这些语句重复执行,所以它的效果类似循环:当quotient的值非零时,把它的值作为初始值重新开始循环。但是,递归调用将会保存一些信息(这点与循环不同),也就好是保存在堆栈中的变量值。这些信息很快就会变得非常重要。

斐波那契数是典型的递归案例:

  Fib(0) = 0 [基本情况] Fib(1) = 1 [基本情况]

  对所有n > 1的整数:Fib(n) = (Fib(n-1) + Fib(n-2)) [递归定义]

 递归算法一般用于解决三类问题:

  (1)数据的定义是按递归定义的。(Fibonacci函数)

  (2)问题解法按递归算法实现。(回溯)

  (3)数据的结构形式是按递归定义的。(树的遍历,图的搜索)

 如:

  procedure a;

  begin

  a;

  end;

  这种方式是直接调用.

又如:

  procedure b;

  begin

  c;

  end;

  procedure c;

  begin

  b;

  end;

  这种方式是间接调用.

如何设计递归算法

  1.确定递归公式

  2.确定边界(终了)条件

原文地址

递归的时间复杂度的求解

递归算法的时间复杂度分析
在算法分析中,当一个算法中包含递归调用时,其时间复杂度的分析会转化为一个递归方程求解。实际上,这个问题是数学上求解渐近阶的问题,而递归方程的形式多种多样,其求解方法也是不一而足,比较常用的有以下三种方法:

方法一:代换法

猜答案,不需要具体猜系数的准确值,只需猜出它的形式,比如猜一个递归式的时间复杂度大概是O(n2),即它的运行时间应该是一个常数乘以n2,可能还会有一些低阶项。
1 猜答案
2 验证递归式按数学归纳法满足条件
EX:
T(n) = 4T(n/2) + n ,T(1) = Θ(1)
猜测T(n) = O(n3)
假设T(k)≤ck3(k

方法二:递归树法

递归树法主要是通过递归树将递归式展开来找到答案,然后再用代换法证明它,因为递归树法是不严谨的。
例如,用递归树法求T(n) = T(n/2) + n2 , 用递归树法将该递归式展开
这里写图片描述

像这样将递归树展开并延伸下去,最终到叶子节点就只剩下T(1),那么该递归树的高度就是logn,因为从顶点n出发,到n/2,到n/4,……最后到1,那么从n到1的折半次数是logn,即高度是logn(应该是一个常数乘以logn,不过没多大关系)。而最下面叶子节点的数目是n,因为从第一层往下,节点数变化为1,2,4,8……,如果树的高度是h,那么就会有2h个叶节点,而高度是logn,那么2logn=n。那么,整体所做的工作加起来就是T(n)了
T(n) = [1+1/2+1/4+……]n2 =2n2,于是可知时间复杂度为T(n) = O(n2)。
再例如,用递归树法求T(n) = T(n/4)+T(n/2)+n2 ,下面用递归树的方法将该递归式展开
这里写图片描述
最后,求叶子节点的数目有点麻烦,因为分支的递归速度是不一样的,左边降低到n/16的时候,右边才降低到n/4,左边子树的高度将会比右边子树的高度要小。可以看到叶子节点的数目必然小于n,因为最开始的问题大小是n,然后递归成一个n/4和n/2的两个子问题,直到最后递归到1停止,而n/4+n/2 < n , 所以最后叶子节点的数目不会超过n,将每层求和就得到T(n),经过观察发现一个等比数列,于是数学归纳法开始派上用场
T(n) = (1+5/16+25/256+……)n2≤2n2 =O(n2) 于是得到该递归式时间复杂度为O(n2),因为是猜出来的等比数列,于是需要用数学归纳法证明之,就又变成方法一中代换法求证了。

方法三:主方法 (master method)

该方法仅适用于特定格式的递归式
这里写图片描述
同时要求f(n)渐进趋正,即当n->无穷时,f(n)>0。
EX:
T(n)=4T(n/2)+n , 则a=4,b=2,f(n)=n,计算nlog(b,a)=n2>f(n), 满足模式一,因此T(n) = nlog(b,a)=O(n2)
T(n)=4T(n/2)+n2,则根据上面计算,满足模式二,因此T(n)=O(n2logn)
T(n)=4T(n/2)+n3,满足模式三,T(n)=O(n3)
对于该方法的正确性,可以通过递归树的方法证明:
在第一层,f(n)分解为a个子问题,每个子问题都是f(n/b),第二层每个子问题又分解为a个子问题,每个问题都是f(n/b2)……这样递归分解下去,最后的叶子还是O(1),整个树的高度就是log以b为底n的对数,整个的叶子节点数目为a的log以b为底n的对数次方(alog(b,n)),即nlog(b,a)个叶子节点。每个分支的递减速度是一样的,将每层都加在一起便得到T(n) ,此时就需要对f(n)的情况进行讨论(于是就是上面的1,2,3)。
原文地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值