递归与非递归的比较

2023-05-16

递归与非递归的比较

非递归效率高;递归代码写出来思路清晰,可读性强。 

      生成可执行文件大小应该和编译器有关吧。。。。

递归的话函数调用是有开销的,而且递归的次数受堆栈大小的限制。 
以二叉树搜索为例: 

bool search(btree* p, int v) 

if (null == p) 
return false; 

if (v == p->v) 
return true 
else 

if (v < p->v) 
return search(p->left, v); 
else 
return search(p->right, v); 



如果这个二叉树很庞大,反复递归函数调用开销就很大,万一堆栈溢出怎么办? 
现在我们用循环改写: 

bool search(btree* p, int v) 

while (p) 

if (v == p->v) 
return true; 
else 

if (v < p->v) 
p = p->left; 
else 
p = p->right; 



return false; 
}

---------------------------------------------------------------------------------------------------------

递归好处:代码更简洁清晰,可读性更好 
递归可读性好这一点,对于初学者可能会反对。实际上递归的代码更清晰,但是从学习的角度要理解递归真正发生的什么,是如何调用的,调用层次和路线,调用堆栈中保存了什么,可能是不容易。但是不可否认递归的代码更简洁。一般来说,一个人可能很容易的写出前中后序的二叉树遍历的递归算法,要写出相应的非递归算法就比较考验水平了,恐怕至少一半的人搞不定。所以说递归代码更简洁明了。 

      递归坏处:由于递归需要系统堆栈,所以空间消耗要比非递归代码要大很多。而且,如果递归深度太大,可能系统撑不住。 

楼上的有人说: 
小的简单的用循环,, 
太复杂了就递归吧,,免得循环看不懂 

      话虽然简单,其实非常有道理:对于小东西,能用循环干嘛要折腾?如果比较复杂,在系统撑的住的情况下,写递归有利于代码的维护(可读性好) 

      另:一般尾递归(即最后一句话进行递归)和单向递归(函数中只有一个递归调用地方)都可以用循环来避免递归,更复杂的情况则要引入栈来进行压栈出栈来改造成非递归,这个栈不一定要严格引入栈数据结构,只需要有这样的思路,用数组什么的就可以。 
至于教科书上喜欢n!的示例,我想只是便于递归思路的引进和建立。真正做代码不可能的。

--------------------------------------------------------------------------------------------------------------------

循环方法比递归方法快, 因为循环避免了一系列函数调用和返回中所涉及到的参数传递和返回值的额外开销。 

      递归和循环之间的选择。一般情况下, 当循环方法比较容易找到时, 你应该避免使用递归。这在问题可以按照一个递推关系式来描述时, 是时常遇到的, 比如阶乘问题就是这种情况。反过来, 当很难建立一个循环方法时, 递归就是很好的方法。实际上, 在某些情形下, 递归方法总是显而易见的, 而循环方法却相当难找到。当某些问题的底层数据结构本身就是递归时, 则递归也就是最好的方法了。

----------------------------------------------------------------------------------------------------------------------

递归其实是方便了程序员难为了机器。它只要得到数学公式就能很方便的写出程序。优点就是易理解,容易编程。但递归是用栈机制实现的(c++),每深入一层,都要占去一块栈数据区域,对嵌套层数深的一些算法,递归会力不从心,空间上会以内存崩溃而告终,而且递归也带来了大量的函数调用,这也有许多额外的时间开销。所以在深度大时,它的时空性就不好了。 
      循环其缺点就是不容易理解,编写复杂问题时困难。优点是效率高。运行时间只因循环次数增加而增加,没什么额外开销。空间上没有什么增加。

----------------------------------------------------------------------------------------------------------------------

递归算法与迭代算法的设计思路区别在于:函数或算法是否具备收敛性,当且仅当一个算法存在预期的收敛效果时,采用递归算法才是可行的,否则,就不能使用递归算法。 
当然,从理论上说,所有的递归函数都可以转换为迭代函数,反之亦然,然而代价通常都是比较高的。 
      但从算法结构来说,递归声明的结构并不总能够转换为迭代结构,原因在于结构的引申本身属于递归的概念,用迭代的方法在设计初期根本无法实现,这就像动多态的东西并不总是可以用静多态的方法实现一样。这也是为什么在结构设计时,通常采用递归的方式而不是采用迭代的方式的原因,一个极典型的例子类似于链表,使用递归定义及其简单,但对于内存定义(数组方式)其定义及调用处理说明就变得很晦涩,尤其是在遇到环链、图、网格等问题时,使用迭代方式从描述到实现上都变得很不现实。

===============================================================================

随笔- 22  文章- 0  评论- 0 

把递归函数转换成非递归程序的一般方法

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

(1). 通过分析,跳过分解过程,直接用循环结构的算法实现求解过程。

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

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

 

●      递归函数的原理 
        用栈保存未完成的工作,在适当的时候从栈中取出并执行。 
        系统保存了工作的数据和状态,数据就是函数的局部变量, 
        状态就是程序指针。 
  
●      非递归程序原理 
        1. 和递归函数的原理相同,只不过是把由系统负责保存工作 
        信息变为程序自己保存,这样能减少保存数据的冗余(主要是 
        节省了局部变量的空间),提高存储效率。 
  
        2. 把程序要完成的工作分成两类:手头工作和保存在栈中的 
        待完成的工作。手头工作指程序正在做的工作。由于某些工作 
        不能一步完成,必须暂缓完成,于是可把它保存在栈中,这就 
        是待完成的工作。 
  
        3. 手头工作必须有其结束条件,不能永远做下去;保存的 
        待完成工作必须含有完成该项工作的所有必要信息。 
  
        4. 程序必须有秩序地完成各项工作。如,可把手头工作恰当 
        处理(直接处理或暂时保存)后,才能继续接手下一步的工作。 
  
        5. 待完成工作必须转换成手头工作才能处理。 
  
●      栈的大小 
        所有递归问题,其递归过程可以展开成一棵树,叶子节点是 
        可解的,按照问题的要求,处理所有叶子节点,就可解决 
        问题本身。可能需要保存(Data, Status),Data是工作数据, 
        Status是工作状态;(Data, Status)决定了整个工作。 
        栈的大小等于树的高度-1,-1是因为根节点不需保存。 
  
●      举例 
例1.    汉诺塔问题 
递归函数: 
void Hanoi(UINT x, UINT y, UINT n) 
// x    Source 
// y    Destination 
// n    Number of plates 

    if (n == 0) return; 
    Hanoi(x, x^y, n-1); 
    Move(x, y); 
    Hanoi(x^y, y, n-1); 

说明:x、y可取1、2、3三数之一,并且x≠y,x^y表示x、y按位异或, 
得到除x、y之外的第三个数。1^2=3, 1^3=2, 2^3=1 
  
非递归程序: 
#define N 5 
tyepdef struct _HANOIDATA 

    UINT x; 
    UINT y; 
    UINT n; 
}HANOIDATA; 
void Hanoi(HANOIDATA hanoiData) 

    HANOIDATA  stack[N]; 
    int        top = -1;      //stack pointer 
  
    while (hanoiData.n || top != -1)    // 存在手头工作或待完成工作 
    { 
        while (hanoiData.n)    // 处理手头工作直到无现成的手头工作, 
                               // 即下次的手头工作必须从栈中取得 
        { 
            hanoiData.n --; 
            stack[++top] = hanoiData;  // 保存待完成工作 
            hanoiData.y ^= hanoiData.x; // 新的手头工作 
        } 
        if (top != -1)  // 存在待完成工作 
        { 
            hanoiData = stack[top--];  // 从栈中取出 
            Move(hanoiData.x, hanoiData.y);   // 直接处理 
            hanoiData.x ^= hanoiData.y; // 未处理完的转换成手头工作 
        } 
    } 

例2. 后根序遍历二叉树 
递归函数: 
void PostTraverse(BINARYTREE root) 

    if (root == NULL) return; 
    PostTraverse(root->LChild); 
    PostTraverse(root->RChild); 
    Visit(root); 

  
非递归程序: 
void PostTraverse(BINARYTREE p) 

    while ( p != NULL || !Stack.IsEmpty() )// 存在工作(手头或待完成) 
    { 
        while (p != NULL)    // 处理手头工作,直到无现成手头工作 
        { 
            Stack.Push(p, RCHILD_AND_ITSELF); 
            p = p->LChild; 
        } 
        if (!Stack.IsEmpty())  // 是否存在待完成工作 
        { 
            Stack.Pop(p, Tag); 
            if (Tag == RCHILD_AND_ITSELF)   // 情况一: RChild &Itself 
            { 
                Stack.Push(p,ONLY_ITSELF)  // 保存待完成工作 
                p = p->RChild; // 新的手头工作 
            } 
            else        //tag == ONLY_ITSELF,  情况二: Only Itself 
            { 
                visit(p); 
                p = NULL;     // 已无现成的手头工作 
            } 
        } 
    } 

  
●      总结 
非递归程序的设计应注意: 
1.      保存暂缓执行的工作 
2.      无现成手头工作的条件 
3.      无待完成工作的条件 
  
程序模式 
void NonRecursiveFunction(DATATYPE Data) 

    while ( ExistHandyWork() || ExistSavedWork() ) 
    { 
        while ( ExistHandyWork() ) 
        { 

            Process(Work, Status)  //Probably push work onto stack 
            NewHandyWork(); 
        } 
        if ( ExistSavedWork() ) 
        { 
            Pop(Work, Status); 
            Process(Work, Status);  //Probably generate new handy work 
        } 
    } 
}

 

 

 

===================================================================

 

      递归算法向非递归算法转换

递归算法实际上是一种分而治之的方法,它把复杂问题分解为简单问题来求解。对于某些复杂问题(例如hanio塔问题),递归算法是一种自然且合乎逻辑的解决问题的方式,但是递归算法的执行效率通常比较差。因此,在求解某些问题时,常采用递归算法来分析问题,用非递归算法来求解问题;另外,有些程序设计语言不支持递归,这就需要把递归算法转换为非递归算法。

    将递归算法转换为非递归算法有两种方法,一种是直接求值,不需要回溯;另一种是不能直接求值,需要回溯。前者使用一些变量保存中间结果,称为直接转换法;后者使用栈保存中间结果,称为间接转换法,下面分别讨论这两种方法。

1. 直接转换法

直接转换法通常用来消除尾递归单向递归,将递归结构用循环结构来替代。

尾递归是指在递归算法中,递归调用语句只有一个,而且是处在算法的最后。例如求阶乘的递归算法:

long fact(int n)

{

  if(n==0) return 1;

  elsereturn n*fact(n-1);

}

当递归调用返回时,是返回到上一层递归调用的下一条语句,而这个返回位置正好是算法的结束处,所以,不必利用栈来保存返回信息。对于尾递归形式的递归算法,可以利用循环结构来替代。例如求阶乘的递归算法可以写成如下循环结构的非递归算法:

long fact(int n)

{

  ints=0;

  for(int i=1; i<=n;i++)

  s=s*i;//用s保存中间结果

  returns;

}

单向递归是指递归算法中虽然有多处递归调用语句,但各递归调用语句的参数之间没有关系,并且这些递归调用语句都处在递归算法的最后。显然,尾递归是单向递归的特例。例如求斐波那契数列的递归算法如下:

int f(int n)

{

  if (n==1 | | n= =0) return 1;

  elsereturn f(n-1)+f(n-2);

}

对于单向递归,可以设置一些变量保存中间结构,将递归结构用循环结构来替代。例如求斐波那契数列的算法中用s1和s2保存中间的计算结果,非递归函数如下:

int f(int n)

{

  int i,s;

  ints1=1, s2=1;

  for(i=3; i<=n; ++i)

        {

        s=s1+s2;

        s2=s1; // 保存f(n-2)的值

        s1=s; //保存f(n-1)的值

  }

  returns;

}

2. 间接转换法

该方法使用栈保存中间结果,一般需根据递归函数在执行过程中栈的变化得到。其一般过程如下:

将初始状态s0进栈

while (栈不为空)

{

  退栈,将栈顶元素赋给s;

  if (s是要找的结果) 返回;

  else

        {

     寻找到s的相关状态s1;

     将s1进栈

  }

}

间接转换法在数据结构中有较多实例,如二叉树遍历算法的非递归实现、图的深度优先遍历算法的非递归实现等等。

使用非递归方式实现递归问题的算法程序,不仅可以节省存储空间,而且可以极大地提高算法程序的执行效率。本文将递归问题分成简单递归问题和复杂递归问题;简单递归问题的非递归实现采用递推技术加以求解,复杂递归问题则根据问题求解的特点采用两类非递归实现算法,使用栈加以实现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

递归与非递归的比较 的相关文章

  • Spring Cloud 入门(2)-- 编写服务消费者

    上一篇文章创建了一个 user service 微服务 xff0c 本文编写一个消费者 xff0c 本文比较简单 1 目标 创建一个 movie service xff0c 该服务器也能够查询用户信息 xff0c 但是内部是通过调用 use
  • jquerymobile-7 导航和多页面固定导航

    在开发的过程中 xff0c 我们经常会遇到在页面的底部有一排按钮 xff0c 我们可以根据这些按钮切换页面 xff0c 或者执行一些动作 在jquerymobile中我们可以在footer和header上添加这样的导航 下面看一个例子代码
  • phonegap入门--4 Camera 摄像头

    今天周六了 每次到了周六就不知道干嘛去 好没劲啊 有在北京的如果周六也没劲的 可以联系我大家一起出去玩 qq598660766 没事干 那就写写博客吧 今天介绍一下Camera这个对象 camera对象提供对设备默认摄像头应用程序的访问 C
  • android focusableInTouchMode设置为true导致OnClick事件失效,点击两次生效

    在开发中遇到focusableInTouchMode ture导致OnClick事件 点击两次生效 导致达不到效果 所以要分析源码解决问题 当在xml布局文件中 设置focusableInTouchMode ture 时 表示触摸事件可以让
  • Android自定义控件基础

    采用自定义控件解决重复编写代码的问题 总共分三步 1 写好一个自定义模板布局 title XML span class hljs pi lt xml version 61 34 1 0 34 encoding 61 34 utf 8 34
  • String,StringBuffer,StringBuilder的区别(优缺点)

    最近学习到StringBuffer xff0c 心中有好些疑问 xff0c 搜索了一些关于String xff0c StringBuffer xff0c StringBuilder的东西 xff0c 现在整理一下 关于这三个类在字符串处理中
  • Android真机获得root权限修改文件权限

    好久没有更新博客了 xff0c 最近因为重装了系统导致所有的配置都不存在了 xff0c 当要修改Android权限去查看数据库文件的时候 xff0c 发现又忘记了怎么去获得修改权限 xff08 其实第一次弄这个内容的时候就费了很大的劲 xf
  • Android完全自定义控件并且实现监听事件

    本篇文章带来Android的完全自定义控件 载体是自定义一个开关的控件 xff0c 并且能够响应事件 xff0c 首先我们先创一个项目 xff0c 名字就叫ToggleView xff0c 修改MainActivity span class
  • Android屏幕适配实战

    说一下在项目里面遇到的一个问题 xff0c 和解决思路 需求来源于运营小姐姐 xff0c 她们希望在App的搜索关键字前面加上图片醒目效果图如下 布局很简单左边一个SimpleDraweeView xff0c 右边一个TextView xf
  • 定制阿里代码检查,实现你自己的代码规范检查

    几个月前 xff0c 阿里开源了p3c xff0c 我也接到了老大交给我的技术改造 是这样的 xff0c app是老项目了 xff0c 半年前接入了ARouter xff0c 由于Activity繁多 xff0c 就没有去全局支持ARout
  • Fresco内部诟病引起的初次从网络加载PNG图片失败

    如题描述 xff0c 这个问题在项目中存在已久 xff0c 今天由于自己的功能在首页 xff0c 初次启动的体验极其糟糕 xff0c 所以硬下头皮把这个问题解决了 先来描述一下怎么样一个差的体验吧 就是当我第一次加载网络PNG xff08
  • 华为OpenEuler体验系列(20)-树莓派 安全渗透测试环境安装

    一 下载镜像 xff1a 地址 xff1a https www openeuler org zh download 二 格式化TF卡和烧录系统 使用过的SD卡 xff0c 用SD Card Formatter格式化SD卡 选择好SD卡后格式
  • Ubuntu开机没有图形界面 进入tty的拯救方法

    Ubuntu开机没有图形界面 进入tty的拯救方法 1 从命令行模式登录2 检查是否可以连网3 安装图形界面参考 由于卸载软件的过程中误删了系统图形界面相关的文件 xff0c 导致开机无法进入图形界面 xff0c 需要通过命令重新安装 1
  • 什么是人工智能?

    Extinguished philosophies lie about the cradle of every science as the strangled snakes beside that of Hercules adapted
  • android 11.0 SystemUI手势上滑显示导航栏和隐藏导航栏

    1 概述 在11 0的产品开发中 对于SystemUI导航栏状态栏的功能定制需求 有要求需要通过上滑来控制导航栏显示 然后3秒钟后自动消失导航栏 实现一个通过手势上滑显示导航栏的需求 2 SystemUI手势上滑显示导航栏和隐藏导航栏相关核
  • 电脑专业英语

    电脑专业英语 1 file n 文件 xff1b v 保存文件 2 command n 命令 xff0c 指令 3 use v 使用 xff0c 用途 4 program n 程序 5 line n 数据 xff0c 程序 行 xff0c
  • 关于极大连通子图与极小连通子图的解释

    对于极大连通子图 xff0c 我们可以把它分成3各部分来看 1 必须是子图 xff08 子图中的顶点 边都是原图的子集 xff09 2 连通 xff08 对于两个顶点u v xff0c 如果存在u到v的边 xff0c 那这两个点就是连通的
  • 公司信息系统架构建设规划

    企业的信息化建设的基础是构建企业的信息系统架构 xff08 也可称之为信息化架构 xff09 xff0c 信息系统架构又由应用架构 数据架构 技术架构和治理架构4部分组成 xff0c 本建议书主要以技术架构 应用架构以及技术架构为对象加以说
  • C#使用rabbitmq (简单例子)

    首先在visual studio项目里面用nuget工具加入 easyNetQ DLL 然后做一个help类 using System using System Collections Generic using System Linq u

随机推荐

  • 我的2013,梦在路上

    我的2013 xff0c 在路上 今年最后一次给姐姐打电话 xff0c 她在那里像我炫耀自己和爸爸妈妈一起跨年 xff0c 说1314的意义 xff0c 而我还在北京苦逼着 回想2013年对于我来说 xff0c 或许是不错的一年 这一年我进
  • 事务是什么?

    事务 xff1a 简单来说 xff0c 事务就是几个操作要作为一个处理单元来完成 xff0c 要么全部完成 xff0c 要么全部不完成 事务可以是一条SQL语句 xff0c 也可以是多条SQL语句或者整个程序 事务日志 xff1a 重做日志
  • 各种加解密算法比较

    一 加密 算法介绍 对称加密算法 对称加密算法用来对敏感数据等信息进行加密 xff0c 常用的算法包括 xff1a DES xff08 Data Encryption Standard xff09 xff1a 数据加密标准 xff0c 速度
  • 系统提示缺少libltdl.so.3

    今天安装heartbeat pils 2 1 4 11 el5 i386 rpm时 xff0c 显示 因为重新安装的linux xff0c 所以以前的一些操作都丢失了 xff0c 安装了一大堆的开发工具 34 Development lib
  • 安装的虚拟机没有了VMnet1

    虚拟的东西终归时有其缺陷的 xff0c 大家安装好虚拟机之后 xff0c 网络适配器中是有VMnat1和VMnat8俩块网卡的 xff0c VMnat1负责主机域虚拟机的host only通信 xff0c 而VMnat8则负责和虚拟机的na
  • mount:No medium found

    使用vmware时 xff0c 科技将iso作为系统的镜像 但是 xff0c 在配置yum源的时候 xff0c 可能会遇到这样的问题 究其原因 xff0c 是由于镜像文件未启动 解决方法 xff1a 右击 xff0c 点击连接 xff0c
  • Android 9.0 Settings 搜索功能屏蔽某个app

    1 概述 在9 0的系统rom产品定制化开发过程中 在系统Settings的开发功能中 最近产品需求要求去掉搜索中屏蔽某个app的搜索 就是根据包名 不让搜索出某个app 在系统setting中 搜索功能中 根据包名过滤掉某个app的搜索功
  • 什么叫跨平台语言

    什么叫跨平台语言呢 xff1f 今天就个人理解简单谈一下 xff0c 还望指正 简单的说 xff0c 就像插座和插头 xff0c 这世界上有没有完全通用的插座呢 xff1f 没有 但是比如某家公司 xff0c 制作了插座和插头 xff0c
  • rpm包管理功能全解

    通常在linux系统中 xff0c 服务是要通过程序来提供的 xff0c 通过调用各种接口编译好之后的源码包文件 xff0c 需要使用rpm xff08 redhat package manager xff09 命令来安装并提供相应的服务
  • 加密

    lt div id 61 34 article content 34 class 61 34 article content clearfix csdn tracking statistics 34 data pid 61 34 blog
  • Ubuntu加域后域账号登录账号串号

    Ubuntu加域后域账号登录账号串号 错误实例原因分析解决办法 错误实例 例如这里用账号test01登录Ubuntu桌面 xff0c 进入桌面后进入终端 test02 64 PCtest01 这里可以看出账号不是test01 原因分析 加入
  • 虚拟机迁移提示设备 “HD audio“ 的备用类型不受支持

    错误原因 尝试 vMotion 虚拟机失败并显示以下错误 xff1a 设备 HD audio 的备用类型不受支持 HD 音频设备在 ESXi 的虚拟机上不受支持 xff0c 并且不能作为通过 vSphere Client 添加的设备 因为图
  • 获取windows10远程桌面记录的用户名密码

    Windows 密码恢复工具 单击此下载链接 输入 download 作为用户名 xff0c 然后 39 nirsoft123 39 作为密码 下载软件包后 xff0c 使用以下密码从中提取文件 xff1a nirsoft123 双击net
  • hisi3516下yuv图片到nnie bgr_u8c3格式转换

    首先要看的sdk文档 xff08 HiIVE API 参考 xff09 其中详细说明了 IVE IMAGE TYPE YUV420SP IVE IMAGE TYPE YUV420P IVE IMAGE TYPE YUV422SP IVE I
  • android 交叉编译dbow3

    ndk 20版本是可以直接过的 xff0c 但是ndk14b时 xff0c 编译报如下错误 xff1a arm linux androideabi gcc error unrecognized command line option 39
  • macOS无法验证此App不包含恶意软件

    换了iMac xff0c 刚用有点不习惯 xff0c 特别是它这安全机制 xff0c 比ubuntu高太多 想用android ndk进行交叉编译 xff0c 里面的很多那种可执行文件 xff0c 会弹出如下错误 解决办法 xff1a 1
  • 初识opencl

    初识opencl 以一个例子开头 以一个例子开头 在自己的笔记本电脑上 win10 安装intel的那个opencl包 xff0c 安装后 xff0c 记得将include与lib包拷贝出来 xff0c 然后在以后的使用中只要链接这个库就o
  • Android 10.0 系统settings系统属性控制一级菜单显示隐藏

    1 概述 在进行定制化开发中 系统settings的一级菜单有些在客户需求中 要求通过系统属性来控制显示隐藏 从而达到控制一级菜单的显示的目的 而系统settings是通过静态加载的方式负责显示隐藏 2 系统Settings一级菜单显示隐藏
  • OpenCL并行加减乘除示例——数据并行

    数据并行化计算与任务并行化分解可以加快程序的运行速度 现在只讲数据并行 下一节讲任务并行 如下基本算术例子 xff0c 输入数组A和数组B xff0c 得到输出数组C xff0c C的结果如图中output所示 A数组如下 xff1a 5行
  • 递归与非递归的比较

    递归与非递归的比较 非递归效率高 xff1b 递归代码写出来思路清晰 xff0c 可读性强 生成可执行文件大小应该和编译器有关吧 递归的话函数调用是有开销的 xff0c 而且递归的次数受堆栈大小的限制 以二叉树搜索为例 xff1a bool