递归与非递归的整理

参考文章:http://www.cnblogs.com/youki/archive/2012/03/18/2404531.html

http://blog.csdn.net/pi9nc/article/details/13008511

http://www.2cto.com/kf/201405/304809.html

1.递归与非递归的区别:

递归的代码量比非递归的代码量少,因为非递归需要额外的变量记录当前所处的位置信息,以及额外的控制语句。而递归所使用的方式是函数调用,这是非常自然的栈结构,不需要记录位置信息,不需要添加控制语句,这些工作都由函数调用的特性解决了。

递归的执行效率比非递归的执行效率低,因为递归的实质是函数调用,而函数调用必然要进行线程栈空间的分配,记录每一次函数调用前的状态等工作,开销是比较大的。而非递归则不需要进行这些工作。在要求高效的很多场合需要将递归程序改写成非递归程序。

递归与非递归调用最主要区别就是在函数调用上。在计算机的工作方式中,函数调用是以栈结构来实现的,最早调用的函数处于栈底,最晚调用的函数处于栈顶,栈中存放的是每个函数中的局部变量等信息,当函数调用返回时该函数相关的信息就会从栈中弹出。

由此可以看出,递归每深入一层,栈的深度也会加一,而且当每一层的递归调用结束,都会自动返回上一层的递归中,因此不需要额外的变量记录当前所处的递归位置,也不需要while、if等控制语句进入或返回上一层或下一层递归。因此代码量比非递归的少。

但正因为函数调用要在栈中进行各种操作,例如分配新的空间,保存当前函数调用的信息,为新的函数调用初始化等,效率比较低下。所以递归的效率比非递归的低。

2.转化

递归是指某个函数或过程直接或间接的调用自身。一般地一个递归包括递归出口和递归体两部分,递归出口确定递归到何时结束,而递归体确定递归求解时的递推关系。递归算法有两个基本特征:一是递归算法是一种分而治之的、把复杂问题分解为简单问题的求解问题方法,对于求解某些复杂问题,递归算法分析问题的方法是有效地;而是递归算法的时间、控件效率通常比较差。因此对解决某些问题时,我们希望用递归算法分析问题,用非递归算法解决问题,这就需要把递归算法转换为非递归算法。

把递归算法转化为非递归算法有如下三种基本方法:

(1). 通过分析,跳过分解过程,直接用循环结构的算法实现求解过程。直接迭代、动态规划什么的,像斐波那契数列就可以用直接迭代写成非递归的;动态规划也是直接迭代的一种,但需要转换思想,提取问题的最优子结构,像数塔问题、归并排序等都属于这类。

(2). 自己用栈模拟系统的运行时栈,通过分析只保存必须保存的信息,从而用非递归算法替代递归算法。

(3). 利用栈保存参数,由于栈的后进先出特性吻合递归算法的执行过程,因而可以用非递归算法替代递归算法。

3.分析

递归深度小于3的,优化结果不明显。大于3的,优化后差距能达到70%以上。

4.实例

汉诺塔

汉诺塔是根据一个传说形成的一个问题:有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至C杆:
每次只能移动一个圆盘;
大盘不能叠在小盘上面。
提示:可将圆盘临时置于B杆,也可将从A杆移出的圆盘重新移回A杆,但都必须遵循上述两条规则。问:如何移?最少要移动多少次?(问题描述转自维基百科)

具体解法思想为先将A杆上面的n-1个借助C杆移到B杆,然后将A杆最低一根直接移到C,再将n-1根借助A杆从B移到C。上面隐含着递归思想将问题逐渐减小最后递推到原问题。

此时注意栈中元素不是单一的,要保留当前状态,可以用结构体,也可以多维数组,下面给出结构体做法。

#include<iostream>
using namespace std;
 
const int MAXN=10000;
int m_Move=0;
 
/**********************************************
*汉诺塔递归解法
***********************************************/
void recurHanoi(char from,char use,char to,int n)
{
    if(0==n)
        return ;
    recurHanoi(from,to,use,n-1);
    cout<<n<<"从"<<from<<"移到"<<to<<endl; ++m_move;="" recurhanoi(use,from,to,n-1);="" }="" void="" recurhanoi(int="" n)="" {="" cout<<"*********递归算法**********"<<endl;="" m_move="0;" recurhanoi('a','b','c',n);="" cout<<"总共移动"<<m_move<<"次"<<endl;="" cout<<"**************************"<<endl;="" **********************************************="" *汉诺塔非递归解法="" ***********************************************="" struct="" node="" int="" number;="" id;="" char="" from;="" use;="" to;="" };="" print(node="" now)="" cout<<now.id<<"从"<<now.from<<"移到"<<now.to<<endl;="" notrecurhanoi(int="" cout<<"********非递归算法*********"<<endl;="" mystack[maxn];="" now;="" top="0;" now.from="'A';now.use='B';now.to='C';now.number=n;now.id=n;" mystack[++top]="now;" from,use,to,number,id;="" while(top="">0)
     {
         if(1==myStack[top].number)
        {
            print(myStack[top]);
            --top;
         }
         else
         {
             from=myStack[top].from;use=myStack[top].use;to=myStack[top].to;number=myStack[top].number;id=myStack[top].id;
             --top;
 
             now.from=use;now.use=from;now.to=to;now.number=number-1;now.id=id-1;
             myStack[++top]=now;
 
             now.from=from;now.use=use;now.to=to;now.number=1;now.id=id;
             myStack[++top]=now;
 
             now.from=from;now.use=to;now.to=use;now.number=number-1;now.id=id-1;
             myStack[++top]=now;     
         }
     }
     cout<<"总共移动"<<m_move<<"次"<<endl; cout<<"**************************"<<endl;="" }="" int="" main()="" {="" n;="" while(cin="">>n)
    {
        recurHanoi(n);
        notRecurHanoi(n);
    }
    return 0;
}</m_move<<"次"<<endl;></n<<"从"<<from<<"移到"<<to<<endl;></iostream>

组合数

C(n,m)=C(n-1,m)+C(n-1,m-1) n>m

C(n,m)=1 n=m或m=0

此问题的递归形式比较简单,非递归形式得深入理解过程,直接栈模拟,注意进出栈的时机,在此不多说可以看看拙劣的代码:

#include<iostream>
using namespace std;
 
const int MAXN=100;
 
int recurCombineCount(int n,int m)
{
    if(0==m||n==m)return 1;
    else return recurCombineCount(n-1,m)+recurCombineCount(n-1,m-1);
}
 
int notRecurCombineCount(int n,int m)
{
    int stack[MAXN][3];
    int top=0;
    ++top;
    stack[top][0]=n;stack[top][1]=m;stack[top][2]=0;
    do
    {
        if(0==stack[top][2])//计算C(n-1,m)
        {
            ++top;
            stack[top][0]=stack[top-1][0]-1;stack[top][1]=stack[top-1][1];stack[top][2]=0;
            if(stack[top][0]==stack[top][1]||0==stack[top][1])
                stack[top][2]=1;
        }
        if(top>=2&&stack[top][2]>0)//计算C(n-1,m-1)
        {
            ++top;
            stack[top][0]=stack[top-2][0]-1;stack[top][1]=stack[top-2][1]-1;stack[top][2]=0;
            if(stack[top][0]==stack[top][1]||0==stack[top][1])
                stack[top][2]=1;
        }
        while(top>=3&&stack[top-1][2]>0&&stack[top][2]>0)//计算C(n,m)
        //注意此处这样有错://if(top>=3&&stack[top-1][2]>0&&stack[top][2]>0)
        {
            stack[top-2][2]=stack[top-1][2]+stack[top][2];
            top-=2;
        }
    }while(top>1);
    return stack[top][2];
}
 
int main()
{
    int n,m;
    while(cin>>n>>m)
    {
        cout<<"************递归算法**************"<<endl; cout<<"结果为:"<<recurcombinecount(n,m)<<endl;="" cout<<"*********************************"<<endl;="" cout<<"***********非递归算法*************"<<endl;="" cout<<"结果为:"<<notrecurcombinecount(n,m)<<endl;="" }="" return="" 0;="" <="" pre="">

二叉树遍历(以先序遍历为例)

先序遍历——按照“根节点-左孩子-右孩子”的顺序进行访问。

递归实现:

void pre_traverse(BTree pTree)  
{  
    if(pTree)  
    {  
        printf("%c ",pTree->data);  
        if(pTree->pLchild)  
            pre_traverse(pTree->pLchild);  
        if(pTree->pRchild)  
            pre_traverse(pTree->pRchild);      
    }  
} 


为了便于理解,这里以下图的二叉树为例。


非递归实现

根据先序遍历的顺序,先访问根节点,再访问左子树,后访问右子树,而对于每个子树来说,又按照同样的访问顺序进行遍历,上图的先序遍历顺序为:ABDECF。非递归的实现思路如下:


对于任一节点P,

1)输出节点P,然后将其入栈,再看P的左孩子是否为空;

2)若P的左孩子不为空,则置P的左孩子为当前节点,重复1)的操作;

3)若P的左孩子为空,则将栈顶节点出栈,但不输出,并将出栈节点的右孩子置为当前节点,看其是否为空;

4)若不为空,则循环至1)操作;

5)如果为空,则继续出栈,但不输出,同时将出栈节点的右孩子置为当前节点,看其是否为空,重复4)和5)操作;

6)直到当前节点P为NULL并且栈空,遍历结束。


下面以上图为例详细分析其先序遍历的非递归实现过程:


首先,从根节点A开始,根据操作1),输出A,并将其入栈,由于A的左孩子不为空,根据操作2),将B置为当前节点,再根据操作1),将B输出,并将其入栈,由于B的左孩子也不为空,根据操作2),将D置为当前节点,再根据操作1),输出D,并将其入栈,此时输出序列为ABD;

由于D的左孩子为空,根据操作3),将栈顶节点D出栈,但不输出,并将其右孩子置为当前节点;

由于D的右孩子为空,根据操作5),继续将栈顶节点B出栈,但不输出,并将其右孩子置为当前节点;

由于B的右孩子E不为空,根据操作1),输出E,并将其入栈,此时输出序列为:ABDE;

由于E的左孩子为空,根据操作3),将栈顶节点E出栈,但不输出,并将其右孩子置为当前节点;

由于E的右孩子为空,根据操作5),继续将栈顶节点A出栈,但不输出,并将其右孩子置为当前节点;

由于A的右孩子C不为空,根据操作1),输出C,并将其入栈,此时输出序列为:ABDEC;

由于A的左孩子F不为空,根据操作2),则将F置为当前节点,再根据操作1),输出F,并将其入栈,此时输出序列为:ABDECF;

由于F的左孩子为空,根据操作3),将栈顶节点F出栈,但不输出,并将其右孩子置为当前节点;

由于F的右孩子为空,根据操作5),继续将栈顶元素C出栈,但不输出,并将其右孩子置为当前节点;

此时栈空,且C的右孩子为NULL,因此遍历结束。

根据以上思路,前序遍历的非递归实现代码如下:

void pre_traverse(BTree pTree)  
{  
    PSTACK stack = create_stack();  //创建一个空栈  
    BTree node_pop;                 //用来保存出栈节点  
    BTree pCur = pTree;             //定义用来指向当前访问的节点的指针  
  
    //直到当前节点pCur为NULL且栈空时,循环结束  
    while(pCur || !is_empty(stack))  
    {  
        //从根节点开始,输出当前节点,并将其入栈,  
        //同时置其左孩子为当前节点,直至其没有左孩子,及当前节点为NULL  
        printf("%c ", pCur->data);  
        push_stack(stack,pCur);  
        pCur = pCur->pLchild;  
        //如果当前节点pCur为NULL且栈不空,则将栈顶节点出栈,  
        //同时置其右孩子为当前节点,循环判断,直至pCur不为空  
        while(!pCur && !is_empty(stack))  
        {  
            pCur = getTop(stack);  
            pop_stack(stack,&node_pop);  
            pCur = pCur->pRchild;              
        }  
    }  
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值