递归函数和非递归函数的转变讲解
递归函数的非递归实现

递归函数的非递归实现递归函数是一种非常常见的编程技巧,但是在某些情况下,递归函数可能会导致栈溢出等问题。
因此,我们可以通过非递归的方式来实现递归函数。
首先,我们需要了解递归函数的本质:递归函数是一种自我调用的函数,每次调用都会将当前的状态保存在栈中,并等待递归结束后依次弹出栈帧,恢复调用时的状态。
因此,非递归实现递归函数的核心思想就是利用栈来保存状态,模拟递归时的栈帧。
具体实现方法如下:1. 把递归函数中的自我调用转换为循环调用,并使用栈来保存每次调用的参数和状态。
2. 每次循环调用时,将参数和状态入栈,并更新参数和状态。
3. 循环调用结束时,从栈中弹出上一个状态,恢复参数和状态。
例如,假设我们要实现一个阶乘函数:```pythondef factorial(n):if n == 1:return 1else:return n * factorial(n-1)```我们可以使用非递归的方式来实现:```pythondef factorial(n):stack = [(n, 1)]res = 1while stack:num, acc = stack.pop()res *= accif num > 1:stack.append((num-1, num))return res```在这个实现中,我们使用栈来保存每次调用的状态,每次循环调用时,将参数和状态入栈,并更新参数和状态。
循环调用结束时,从栈中弹出上一个状态,恢复参数和状态。
最终得到阶乘的结果。
总的来说,非递归实现递归函数可以避免栈溢出等问题,同时也可以提高代码的执行效率。
但是,非递归实现递归函数可能会影响代码的可读性和维护性,需要根据具体情况进行权衡。
计算机二级考试C语言辅导:浅谈递归机制和非递归转换

⼀、什么是递归 很多数据结构的定义都是根据递归性质来进⾏定义的,是因为这些结构固有的性质。
递归是指某个函数直接或间接的调⽤⾃⾝。
问题的求解过程就是划分成许多相同性质的⼦问题的求解,⽽⼩问题的求解过程可以很容易的求出,这些⼦问题的解就构成⾥原问题的解了。
⼆、递归的⼏个特点 1.递归式,就是如何将原问题划分成⼦问题。
2.递归出⼝,递归终⽌的条件,即最⼩⼦问题的求解,可以允许多个出⼝。
3.界函数,问题规模变化的函数,它保证递归的规模向出⼝条件靠拢 三、递归的运做机制 很明显,很多问题本⾝固有的性质就决定此类问题是递归定义,所以递归程序很直接算法程序结构清晰、思路明了。
但是递归的执⾏过程却很让⼈费解,这也是让很多⼈难理解递归的原因之⼀。
由于递归调⽤是对函数⾃⾝的调⽤,在⼀次调⽤没有结束之前⼜开始了另外⼀次调⽤,按照作⽤域的规定,函数在执⾏终⽌之前是不能收回所占⽤的空间,必须保存下来,这也就意味着每⼀次的调⽤都要把分配的相应空间保存起来。
为了更好管理这些空间,系统内部设置⼀个栈,⽤于存放每次函数调⽤与返回所需的各种数据,其中主要包括函数的调⽤结束的返回地址,返回值,参数和局部变量等。
其过程⼤致如下: 1.计算当前函数的实参的值 2.分配空间,并将⾸地址压栈,保护现场 3.转到函数体,执⾏各语句,此前部分会重复发⽣(递归调⽤) 4.直到出⼝,从栈顶取出相应数据,包括,返回地址,返回值等等,收回空间,恢复现场,转到上⼀层的调⽤位置继续执⾏本次调⽤未完成的语句。
四、引⼊⾮递归 从⽤户使⽤⾓度来说,递归真的很简便,对程序宏观上容易理解。
递归程序的时间复杂度虽然可以根据T(n)=T(n-1)*f(n)递归求出,其中f(n)是递归式的执⾏时间复杂度,⼀般来说,时间复杂度和对应的⾮递归差不多,但是递归的效率是相当低的它主要发费在反复的进栈出栈,各种中断等机制上(具体的可以参考操作系统)更有甚者,在递归求解过程中,某些解会重复的求好⼏次,这是不能容忍的,这些也是引⼊⾮递归机制的原因之⼀。
递归函数和非递归函数的转变讲解

一般地,一个递归模型是由递归出口和递归体两部 分组成, 前者确定递归到何时结束, 后者确定递归求解 时的递推关系。
递归出口的一般格式如下:
f(s1)=m1
(6.1)
这里的s1与m1均为常量,有些递归问题可能有几个 递归出口。
递归体的一般格式如下:
f(sn+1)=g(f(si),f(si+1),…,f(sn),cj,cj+1,…,cm)
第6章 递归
6.1 什么是递归 6.2 递归算法的设计 6.3 递归算法到非递归算法的转换
本章小结
6.1 什么是递归
6.1.1 递归的定义
在定义一个过程或函数时出现调用本过程或本函 数的成分,称之为递归。若调用自身,称之为直接递归。 若过程或函数p调用过程或函数q,而q又调用p,称之 为间接递归。
6.1.3 递归模型
递归模型是递归算法的抽象,它反映递归问题的递 归结构,例如,前面的递归算法对应的递归模型如下:
fun(0)=1 fun(n)=n*fun(n-1)
n=0 (1) n>0 (2)
其中:第一个式子给出了递归的终止条件,我们称 之 为 递 归 出 口 ; 第 二 个 式 子 给 出 了 fun(n) 的 值 与 fun(n-1)的值之间的关系,我们称之为递归体。
return(x); }
在该函数factorial (n)求解过程中,直接调用factorial (n-1)自身,所以它是一个直接递归函数。
int factorial(int n)
n=4 n=3 n=2 n=1 n=0
{ int x;
if (n==0)
x=1;
else
x=factorial (n-1)*n;
北大数据结构课件,内部资料,精品

栈的应用递归到非递归的转换张 铭 北京大学信息学院1内容提要递归函数调用原理 机械的递归转换 优化后的非递归函数 非递归的二叉树周游2张铭 北京大学信息学院递归函数示例void exmp(int n, int& f) { int u1, u2; if (n<2) f = n+1; else { exmp((int)(n/2), u1); exmp((int)(n/4), u2); f = u1*u2; } } 张铭3北京大学信息学院数学公式fu (n) ={n +1当n < 2时fu ( ⎣n / 2 ⎦)* fu ( ⎣n / 4 ⎦)n ≥ 2时}4张铭 北京大学信息学院函数调用及返回的步骤调用– 保存调用信息(参数,返回地址) – 分配数据区(局部变量) – 控制转移给被调函数的入口返回– 保存返回信息 – 释放数据区 – 控制转移到上级函数(主调用函数)5张铭 北京大学信息学院函数执行过程图解二叉树图解 Exmp(7,&f) f=u1*u2=4u1=f=2 u2=f=2 Exmp(3,&f) Exmp(1,&f) f=u1*u2=2u1=f=2 Exmp(1,&f)张铭u2=f=1 Exmp(0,&f)6北京大学信息学院用栈模拟递归调用过程后调用,先返回(LIFO),所以用栈rd=1: n=1 f=1 u1=? u2=? f=2 rd=2: n=0 f=? u1=? u2=? f=? rd=2: n=1 f=? u1=2 u2=1 rd=1: n=3 f=? u1=? u2=? f=2 u1=? u2=? rd=3: n=7 f=? u1=? u2=? u1=2 u2=2张铭7北京大学信息学院递归过程的模拟假设 void recfunc(p1, p2, p3,…,pk, pk+1, …, pm) 是一个递归函数void是无返回值型的函数,如果有返回值, 我们可以把返回值转换为一个引用型参数 其中参数p1, p2, p3,…,pk是值传递,参 数pk+1, …, pm是引用传递。
c语言递归函数的非递归实现

c语言递归函数的非递归实现递归函数是指在函数内部调用自身的函数,这种函数调用方式能够简化问题的表达和解决。
但是在实际编程中,递归函数可能会消耗大量的系统资源,因此有时候需要将递归函数改写为非递归形式,以减少资源消耗。
接下来将介绍如何在C 语言中实现递归函数的非递归形式。
一种常见的方法是使用栈来模拟递归函数的调用过程。
在递归函数中,每次调用都会将函数的参数、局部变量和返回地址保存在栈中,而函数执行完毕后会从栈中取出这些信息以便返回上一层调用。
因此,我们可以自己创建一个栈来模拟这个过程。
首先,我们需要定义一个结构体来表示栈的节点,包括函数的参数、局部变量和返回地址等信息。
接着,我们可以创建一个栈,用来保存这些节点。
在非递归函数中,我们首先将函数的初始参数压入栈中,然后开始一个循环,每次循环中执行函数的一次递归调用。
在调用函数时,我们将函数的参数和局部变量保存在一个节点中,然后将这个节点压入栈中。
当函数执行完毕后,我们从栈中弹出这个节点,取出其中的参数和局部变量,以便返回上一层调用。
通过这种方式,我们可以在不使用递归的情况下实现递归函数的功能。
这样一来,我们可以减少系统资源的消耗,提高程序的效率。
当然,这种方法在一些情况下可能会增加代码的复杂性,但在需要避免递归调用的情况下,这是一种可行的替代方案。
总的来说,非递归实现递归函数的方法是使用栈来模拟函数调用的过程,将函数的参数、局部变量和返回地址保存在栈中,以便在函数执行完毕后能够返回上一层调用。
通过这种方式,我们可以在不消耗过多系统资源的情况下,实现递归函数的功能。
这种方法在一些情况下可能会增加代码的复杂性,但是在需要避免递归调用的情况下,是一种有效的解决方案。
递归算法向非递归转换的一般规则

科技信息
0计算机与信 息技术0
S IN E IF R TO CE C O MA I N N
20 年 06
第 l 期 l
递归算法向非递归转换的一般规则
李 红字
( 尔滨 师范 大学 阿城 学院计 算 机与信 息技 术 系 黑 龙江 阿城 哈
100 ) 5 3 1
摘要 : 归程序结构简单、 递 清晰, 可读性 好 , 易于验证其 正确性 。 浪费空问且执行效率低 。因此 , 且 但 有时需要 把递归算法转换成非递归算 法。本文培 出了一种根据递归调 用的内部 实现原理把递归算法向非递归转换的一般规则。 最后 , 明非递归化 应谊注意的一些问题 。 说
关键பைடு நூலகம்词 : 归 ; 归 算 法 ; 递 递 非递 归 算 法 ; 栈 堆
vi ot dr ie o p s re( t et d o Br ) 递归是一个重要 的课题 。 在计算机科学 和数学领域 中 , 从理论到 {f( i0 实践 , 都得到广泛的应用 。 在计算机科学中为递 归下 的定义是 : 若一个 { otre( >ci ) ps d r- l l ; o t h d 过 程 直 接 地 或 间接 地 调 用 自 己 , 称 这 个 过 程是 递 归 的 过 程 。数 学 上 则 p s re( >c i ) ot dr - rhl ; o t d 常 用 的 阶乘 函数 、幂 指 数 、级 数 、io ac 数 列 、= 项 式 系 数 、组 合 Fb nci vset> aa; it( dt) i - C 勒 让 德 多 项 式 等 。 们 的 定 义 和计 算 都 是 递 归 的 : 据 结 构 中的 m、 它 数 】 树、 二叉树和广义表也是通 过递归方式加 以定义的 。 由于其本身 固有 } 的 递 归性 质 , 而关 于 它 们 许 多 问 题 的算 法 均 可 以 采 用 递 归 技 术 加 以 因 转换 程 序 如下 : 实 现 ; 有一 些 复杂 的 问题 用 递 归 解 很 简 单 , 汉 诺 塔 问题 、 宫 的求 还 如 迷 vi otre(ie odp s d r t et o Br ) 解等等。 { 解 决 递 归 问题 的算 法 称 为 递 归 算 法 。 归 算 法 的本 质 是按 分 而治 递 Ii tc()p t / 则 1 nt akS; -; / S 规 之算法的思想 ,把一・ 个较复杂问盾的处理归结为较简单问题 的处理 , E: 0 / 则 2 / 规 并 逐 步 把 它 归结 为最 简 单 情 况 的处 理 , 而 解 决 问 题 。 递 归 算 法 的执 从 ip f) ( / 则5 / 规 行 过 程 可 分 为 自上而 下 的 回推 和 白底 而 上 的 递 推 两 个 阶段 。 具 体 实 在 {ut , L ) / 则 3 p s(P, 1;/ S 规 现时 , 是借助系统提供 的栈空 间来完成的。 p p >ci ; / 则 3 = 一 1hl d / 规 利 用 递 归 编写 出来 的程 序 , 构 清 晰 . 读 性 好 , 且 它 的正 确 性 结 可 而 gt 0 ooE ; , 则 3 , 规 容易得到证明 。 然而 , 实 际应 用 时 也 存 在 一 些 问 盾 。 要 表 现 在 以下 在 主 L: I / 则 3 / 规 P s(, L ) , 则 3 uhS P, 2; , 规 几方面 : 一, 第 它需要不 断调用 自身 , 至使程序运行 的效率很低 。 因此 p p >ci ; , 则 3 = - rhl , d 规 在希望节省存储空间和追求执行效率 的情况下 。 人们更希望使用非递 归方 式 求 解 的算 法 程 序 ; 二 , 些 高 级 程 序 设 计 语 言 没 有 提 供 递 归 第 有 gt 0 o E; o , 则3 , 规 的机制和手段 , 对于某些 具有递归性质 的问题 . 必须使 用非递归方式 L: 2 , 则3 , 规 解 决 ; 三 . 究 递 归 算 法 的非 递 归 实 现 有 助 于 透 彻 理 解 递 归 机 制 . 第 研 而 Vit(一 dt) / 则 5 sep > aa 规 i ;/ 这种理解是熟练掌握递归程序技能的必要前提。 下面本 文主要 根据递 】 归 内部 实 现 原 理 给 出 ・ 非 递 归 算 法 实 现 的 规 则 。 种 i Sak mpys / 则 4 f t E t ) / c () 规 { o(, , ) ppS P x; , 则4 , 规 1递 归 算 法 向非 递 归 转 化 的 一 般 规 则 . 11 递 归算 法 转 换 成 非 递 归 算 法 完 全 模 拟 了递 归 函数 的 系 统执 .把 gt oox: / 则4 / 规 行 过程。 这样构造 的非递归算法通常语句较多 . 但因有固定方法可循 。 】 故在实际软件设计 中也经常他用 。具体转换规则如下 : 】 ( ) 始 化 栈 S 1初 。 在 构 造 程 序 的 流 程 图时 , 到 多 个 返 回 地 址 时 。 用 分 支 结 构 实 遇 要 () 2 给每个递归函数的人 口分别标 以不 同的标号 ( 如从 E O至E ) 现 ( 或 s i h n, i wt 语句 )本程序流程 图如下 ( 1 : f c , 图 ) 并 在 每 个 返 回 处设 立 一 个 标 号 “(≥ 1 。 i ) () 3 将每个递归函数 中的每个递 归调用转化为如下操作。 a保 留 现 场 ; 辟 栈 顶 存 储 空 间 。 调用 层 中 的 局 部 变 量 、 式 参 . 开 将 形 数 与返 回地 址 保 存 在栈 中 ( 外层 调 用不 必 考 虑 ) 最 ; b准备 数据 : . 为被调用子 程序准备 所需的参 数 . 即计算实在参 数 的值 , 赋 给 对 应 的 形 参 : 并 c 转 入相 应 的进 归 函 数 执 行 ( . 即如 果 被 调 用 函数 为 E .则 gt i oo
递归函数解释

递归函数解释
一、递归定义
递归函数是指一个函数在其定义域内,存在某个或某些子集作为其自身的输入,也就是一个函数直接或间接调用自身的一种过程。
通常递归函数的定义会包含两部分:基本情况和递归情况。
二、基本情况
基本情况也被称为基线条件,是递归函数的结束条件。
基本情况决定了递归何时停止,它必须是直接可求解的情况。
基本情况是递归函数的根,其他递归情况都由它导出。
三、递归转化
递归转化是将问题转化为更小的同类问题的一种策略。
在递归函数中,每一个递归调用都是为了解决一个更小的子问题,以便于通过这些子问题的解来找到原问题的解。
四、递归终止
递归终止是指函数在完成一系列递归调用后,最终达到基本情况并返回结果的过程。
递归终止是递归函数的终点,也是递归函数开始返回结果的地方。
五、递归层次
递归层次是指函数在执行过程中所经历的递归深度。
每一个递归调用都会增加一层的递归深度,直到达到基本情况并返回结果,然后开始逐层返回,直到完成所有递归调用。
理解递归层次有助于更好地理解递归函数的执行过程和时间复杂度。
递归算法和非递归算法的区别和转换

递归算法向非递归算法转换递归算法实际上是一种分而治之的方法,它把复杂问题分解为简单问题来求解。
对于某些复杂问题(例如hanio塔问题),递归算法是一种自然且合乎逻辑的解决问题的方式,但是递归算法的执行效率通常比较差。
因此,在求解某些问题时,常采用递归算法来分析问题,用非递归算法来求解问题;另外,有些程序设计语言不支持递归,这就需要把递归算法转换为非递归算法。
将递归算法转换为非递归算法有两种方法,一种是直接求值,不需要回溯;另一种是不能直接求值,需要回溯。
前者使用一些变量保存中间结果,称为直接转换法;后者使用栈保存中间结果,称为间接转换法,下面分别讨论这两种方法。
1. 直接转换法直接转换法通常用来消除尾递归和单向递归,将递归结构用循环结构来替代。
尾递归是指在递归算法中,递归调用语句只有一个,而且是处在算法的最后。
例如求阶乘的递归算法:long fact(int n){if (n==0) return 1;else return n*fact(n-1);}当递归调用返回时,是返回到上一层递归调用的下一条语句,而这个返回位置正好是算法的结束处,所以,不必利用栈来保存返回信息。
对于尾递归形式的递归算法,可以利用循环结构来替代。
例如求阶乘的递归算法可以写成如下循环结构的非递归算法:long fact(int n){int s=0;for (int i=1; i<=n;i++)s=s*i; //用s保存中间结果return s;}单向递归是指递归算法中虽然有多处递归调用语句,但各递归调用语句的参数之间没有关系,并且这些递归调用语句都处在递归算法的最后。
显然,尾递归是单向递归的特例。
例如求斐波那契数列的递归算法如下:int f(int n){if (n= =1 | | n= =0) return 1;else return f(n-1)+f(n-2);}对于单向递归,可以设置一些变量保存中间结构,将递归结构用循环结构来替代。
数据结构与算法—递归与非递归转换

递归与非递归转换的基础知识是能够正确理解三种树的遍历方法:前序,中序和后序,第一篇就是关于这三种遍历方法的递归和非递归算法。
一、为什么要学习递归与非递归的转换的实现方法?1>并不是每一门语言都支持递归的。
2>有助于理解递归的本质。
3>有助于理解栈,树等数据结构。
二、三种遍历树的递归和非递归算法递归与非递归的转换基于以下的原理:所有的递归程序都可以用树结构表示出来。
需要说明的是,这个”原理”并没有经过严格的数学证明,只是我的一个猜想,不过在至少在我遇到的例子中是适用的。
学习过树结构的人都知道,有三种方法可以遍历树:前序,中序,后序。
理解这三种遍历方式的递归和非递归的表达方式是能够正确实现转换的关键之处,所以我们先来谈谈这个。
需要说明的是,这里以特殊的二叉树来说明,不过大多数情况下二叉树已经够用,而且理解了二叉树的遍历,其它的树遍历方式就不难了。
1>前序遍历a>递归方式:void preorder_recursive(Bitree T> /* 先序遍历二叉树的递归算法 */{if (T> {visit(T>。
/* 访问当前结点 */preorder_recursive(T->lchild>。
/* 访问左子树 */preorder_recursive(T->rchild>。
/* 访问右子树 */}}b>非递归方式void preorder_nonrecursive(Bitree T> /* 先序遍历二叉树的非递归算法 */initstack(S>。
push(S,T>。
/* 根指针进栈 */while(!stackempty(S>> {while(gettop(S,p>&&p> { /* 向左走到尽头 */visit(p>。
/* 每向前走一步都访问当前结点 */push(S,p->lchild>。
递归算法修改为非递归算法

递归算法修改为非递归算法1.引言1.1 概述递归算法是一种常见的计算方法,它通过将大问题拆分成一个或多个相似的子问题来解决。
递归算法通常通过不断调用自身来处理这些子问题,直到达到基本情况时停止递归。
递归算法的设计具有简洁、优雅的特点,并在某些情况下能够提供高效的解决方案。
然而,递归算法也存在一些局限性。
首先,递归调用会带来额外的函数调用开销,可能会导致程序运行速度较慢。
其次,递归算法对于大规模问题的解决可能会导致堆栈溢出的问题,因为每一次递归调用都需要在内存中保存一些信息。
为了克服递归算法的局限性,我们可以将其修改为非递归算法。
非递归算法使用迭代的方式来解决问题,通过使用循环结构来代替递归调用,从而降低了函数调用的开销。
此外,非递归算法更加直观易懂,可以减少程序的复杂性。
本文将探讨递归算法修改为非递归算法的必要性和优势,并通过具体的例子和算法讲解,详细介绍如何将递归算法转化为非递归算法的思路和方法。
通过对比和分析,我们将展示非递归算法在某些情况下的效率和可行性,以及它在解决问题时的优势和不足。
在接下来的章节中,我们将首先介绍递归算法的特点,包括递归调用和基本情况的判断。
然后,我们将讨论非递归算法的优势,包括降低函数调用开销和提高程序运行效率的能力。
最后,我们将总结递归算法的局限性,强调修改为非递归算法的必要性。
1.2 文章结构本文分为引言、正文和结论三个部分。
在引言部分,我们将概述本文的主题和目的,并介绍文章的结构。
在正文部分,我们将首先探讨递归算法的特点,包括递归算法的定义、实现方式和应用场景等。
然后我们将分析非递归算法的优势,包括非递归算法相对于递归算法的效率、可读性和内存消耗等方面的优势。
接下来,在结论部分,我们将讨论递归算法的局限性,包括递归算法在处理大规模数据时存在的问题和栈溢出的风险等。
最后,我们将强调修改为非递归算法的必要性,以解决递归算法的局限性,并总结本文的主要观点和结论。
通过以上结构,我们将全面而系统地探讨递归算法修改为非递归算法的背景、原因和优势,以及非递归算法在解决问题中的应用价值。
从递归算法到非递归的变换

Ab t a t: s r c Thi p r ma s a s ud r o ys f r t a s o m i e ur i e a g ihms t on r — s pa e ke t y ofva i us wa o r n f r ng r c s v l ort o n — e c s v l rt m s, ur i e a go ih whih h l o i c e s he e fc e c o o a mi c e p t n r a e t fi i n y f r pr gr m ng.A fe tv o u i n o t e e f c i e s l to t h
Ke r s:e ur i e; t c c i ls ; na y—r e y wo d r c sv s a k; ha n—it bi r t e
一
般 而 言 , 归 算 法 与 实 际 问 题 的 表 达 相 近 , 别 符 合 人 们 的 思 维 习 惯 , 多 数 学 函 数 也 是 用 递 归 来 表 达 , 具 有 简 递 特 许 它
归或间 接递归.
鉴 于 上 述 多 方 面 的 原 因 , 握 递 归 算 法 的 非 递 归 实 现 , 有 利 于 程 序 的移 植 性 , 时 对 初 学 者 来 说 , 有 效 地 培 养 、 掌 将 同 将
提高其算 法设计能 力.
1 递 归 算 法 的 非 递 归 实 现
下 面 将 用 栈 、 环 迭 代 等 方 法 来 实 现 非 递 归 化 , 法 均 以类 P sa 进 行 描 述 . 循 算 acl 1 1 用 迭 代 来 实 现 递 归 的 非 递 归 化 .
从 递 归 算 法 到 非 递 归 的变 换
算法设计中递归转为非递归探讨

对于递归函数而言, 若某条赋值语句中包含 两处或多处递归调用 ( 假设为 n 处) , 则应首先把 它拆成 n 条赋值语句, 使得每条赋值语句只包含 一处递归调用, 同时对增加的 n- 1 条赋值语句, 要增设 n- 1 个局部变量, 然后按以上六条规则转 换成非递归函数。
a [ top] : = n; n: = n- 1; { 实参减 1}
goto 1; { 转向开 始, 继 续}
end ; 2: f1: = n* f1; { 根据 n 和 f ( n- 1) , 求 f ( n) } 3: if top> 0 then begin n: = a [ top] ; { 做返回处理}
设 P 是一个递归算法, 假定 P 中共有 m 个值 参和局部变量, 共有 t 处递归调用 P 的语句, 则 把 P 改写成一个非递归算法的一般规则为:
定义一个栈 S, 用来保存每次递归调用前值参 和局部变量的当前值以及调用后的返回地址。即 S 应该含有 m+ 1 个域, 且 S 的深度必须足够大, 使 得递归过程中不会发生栈溢出。
[ 收稿日期] 2005- 07- 04 [ 作者简介] 廖慧芳, 女, 助教, 研究方向: 计算机算法设计。
2005 年第 4 期
九江学院学报 ( 自然科学版)
15
( n) 时, 系统首先为后面的递归调用建立一 个含 有 3 个域 ( 值参 n, 局部变量 k 和一个返回地址) 的栈; 在每次执行递归调用 tran ( n) 前, 系统自 动把 n 和 k 的当前值以及 write ( k: 1) 语句 的开 始位置 ( 即调用结束后的返回地址) 压栈; 在每 次执行到最后的 end 语句 ( 即一次递归调用结束) 后, 又自动把栈顶的与 n 和 k 对应的值分别赋给 n 和 k ( 出栈) , 接着无条件转向 write ( k: 1) 语句 的开始位置继续向下执行程序。 3 递归的缺点
递归算法及其转化为非递归算法的分析

高 鹭 周李 涌
( 内蒙古科技大 学信息 工程 学 院 内蒙古包 头
摘
0 0 ) 1 1 4 0
要 : 归是 程序设 计中 强有 力的工 具, 递 同时也 有 着鲜 明的优缺 点 , 曩 学习的难点 。本文从递 归 的概念 、递 归的实现和递 归 与非递 也
必须 具 备 终 止 递 归 的 条 件 。 程 序 中 只 能 出 现 有 限次 的 ,有 终 止 的递 归调 用 。 即 通过 转 化 过程 , 在某 一 特定 的 条件 下 , 以 得到 可 定 解 ,而 不再 使用 递 归 定 义 。 2. 递 归算法 设计 的实质 2 设 计 递 归 算 法 时 ,通 常 可 以 写 出 问题 求 解 的 递 归 定 义 ,递 归 定 义 由 基 本项 和 归 纳 项 组 成 。基 本 项 描 述 了 一 个 或 几 个 递 归 过 程 的 终 结 状 态 。 归 纳 项 描 述 了如 何 实 现 从当前状态到终结状态的转化 。 递 归设 计 的实 质是 :当一 个 复 杂 的 问 题 可 以 分 解 成若 干子 问 题 来 处 理 时 ,其 中 某 些 子 问 题 与 原 问 题 有 相 同 的 特 征 属性 , 则可利用和原 问题相 同的分析处理方 法 ; 反之 , 些 子 问题 解决 了 , 这 原问 题就 迎 刃 而 解 了 。递 归定 义 的 归 纳 项 就 是 描 述 这 种 原 问 题 和 子 问题 之 间的 转 化 关 系 。以 递 归方 法求 n! 例 , 以把 它分 解成 两个 子 问题 : 为 可 ①求 0 1 或 的阶乘 ; ②求 n与( 1 !的 乘积。 n ) 其 中( n一1 !的子 问题 和 原 问题 特征 属性 ) 相 同, 只是参 数( -l n 不 同, n 和 ) 由此实现 了 递 归 。递 归算 法 如 下 : ln a ( n )/ o g fc1 g n { /递 归实现 n! o i n O f< ) ( rt r ro ; eu n e r r in 01= ) r un 1 / f == 1 =1 e r ; /终 ( n t
二叉树的递归及非递归的遍历及其应用

二叉树的递归及非递归的遍历及其应用二叉树是一种常见的数据结构,由节点和边组成,每个节点最多有两个子节点,分别称为左子节点和右子节点。
递归和非递归是两种遍历二叉树的方法,递归是通过递归函数实现,而非递归则利用栈的数据结构来实现。
二叉树的遍历是指按照一定的顺序访问二叉树中的每个节点。
常见的遍历方式有前序遍历、中序遍历和后序遍历。
1.前序遍历(Preorder Traversal):在前序遍历中,首先访问根节点,然后递归地遍历左子树和右子树。
遍历顺序为:根节点-左子树-右子树。
2.中序遍历(Inorder Traversal):在中序遍历中,首先递归地遍历左子树,然后访问根节点,最后递归地遍历右子树。
遍历顺序为:左子树-根节点-右子树。
3.后序遍历(Postorder Traversal):在后序遍历中,首先递归地遍历左子树和右子树,最后访问根节点。
遍历顺序为:左子树-右子树-根节点。
递归遍历方法的实现相对简单,但可能存在性能问题,因为递归调用会导致函数的调用和返回开销,尤其是在处理大规模二叉树时。
而非递归遍历方法则能够通过利用栈的特性,在迭代过程中模拟函数调用栈,避免了函数调用的性能开销。
非递归遍历二叉树的方法通常利用栈来实现。
遍历过程中,将节点按照遍历顺序入栈,然后访问栈顶元素,并将其出栈。
对于前序遍历和中序遍历,入栈顺序不同,而对于后序遍历,需要维护一个已访问标记来标识节点是否已访问过。
以下是非递归遍历二叉树的示例代码(以前序遍历为例):```pythonclass TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightdef preorderTraversal(root): if not root:return []stack = []result = []stack.append(root)while stack:node = stack.pop()result.append(node.val)if node.right:stack.append(node.right)if node.left:stack.append(node.left)return result```在实际应用中,二叉树的遍历方法有很多应用。
递归算法转换为非递归算法

递归算法转换为非递归算法想象一下,递归就像是你的好朋友,老是在你耳边叨叨,告诉你:“再来一次,再来一次。
”而非递归就像是那个实在的家伙,给你一份清晰的计划,告诉你该干嘛。
用非递归的方式,咱们就能更好地控制进度,不会再像在黑暗中摸索,而是一步步地稳稳前进。
说到这里,有的人可能会问:“非递归的难度是不是太高了?”其实呢,非递归并没有想象中那么复杂,特别是如果你能掌握一些基本的技巧,就像玩拼图一样,轻松上手。
举个例子,大家都知道计算斐波那契数列吧。
那种用递归的方式计算,简直像在爬一座高山,一级一级上去,最后发现自己爬了个寂寞,特别是数字一多,简直就是给自己挖坑。
非递归的方法其实就是把这个过程变得更直白,像是在教你怎么把山坡变成一条平坦的大路。
我们可以用一个简单的循环来实现,拿着一张小纸条,把每一步都写下来。
随着每一轮的循环,数据就一点点填满,直到达成目标。
就像煮面一样,慢慢煮,耐心等待,最后面条总会熟透的。
再说个有趣的事儿,遍历树结构的时候,递归的方法就像你在给小朋友讲故事,每讲完一层就得回到上一层,像是在玩捉迷藏,兜兜转转。
可是,非递归就不一样了,咱们可以用一个栈,把要访问的节点先都存起来,等到该去的时候,再一层层取出来,就像用一根绳子把它们串起来,拉出来的时候,结果就乖乖地在眼前了。
想象一下,树上的苹果,一个一个摘下来,效率简直高得惊人,没那么多折腾。
非递归的方式往往能让代码变得更加清晰,特别是当你给别人分享你的代码时,大家都能看得明白,不会一脸懵逼。
这样的好处简直就像是给大家开了一扇窗,阳光照进来,温暖了整个房间。
哎,真的是好得不得了。
说到大家不妨试试把自己的递归代码转换成非递归,可能会觉得自己像获得了一种新技能。
学会这一招,编程之路就更加宽广了,就像是打开了一扇新天地,眼前的风景愈加美丽。
不论是递归还是非递归,各有千秋,关键是看你自己怎么用。
编程的乐趣在于探索,敢于尝试,保持好奇心,那才是最重要的!好啦,今天的分享就到这里,祝大家编程愉快,玩得开心!。
递归算法转换为非递归算法的技巧

递归算法转换为⾮递归算法的技巧递归函数具有很好的可读性和可维护性,但是⼤部分情况下程序效率不如⾮递归函数,所以在程序设计中⼀般喜欢先⽤递归解决问题,在保证⽅法正确的前提下再转换为⾮递归函数以提⾼效率。
函数调⽤时,需要在栈中分配新的帧,将返回地址,调⽤参数和局部变量⼊栈。
所以递归调⽤越深,占⽤的栈空间越多。
如果层数过深,肯定会导致栈溢出,这也是消除递归的必要性之⼀。
递归函数⼜可以分为尾递归和⾮尾递归函数,前者往往具有很好的优化效率,下⾯我们分别加以讨论。
尾递归函数尾递归函数是指函数的最后⼀个动作是调⽤函数本⾝的递归函数,是递归的⼀种特殊情形。
尾递归具有两个主要的特征:1. 调⽤⾃⾝函数(Self-called);2. 计算仅占⽤常量栈空间(Stack Space)。
为什么尾递归可以做到常量栈空间,我们⽤著名的fibonacci数列作为例⼦来说明。
fibonacci数列实现⽅法⼀般是这样的,int FibonacciRecur(int n) {if (0==n) return 0;if (1==n) return 1;return FibonacciRecur(n-1)+FibonacciRecur(n-2);}不过需要注意的是这种实现⽅法并不是尾递归,因为尾递归的最后⼀个动作必须是调⽤⾃⾝,这⾥最后的动作是加法运算,所以我们要修改⼀下,int FibonacciTailRecur(int n, int acc1, int acc2) {if (0==n) return acc1;return FibonacciTailRecur(n-1, acc2, acc1+acc2);}好了,现在符合尾递归的定义了,⽤gcc分别加-O和-O2选项编译,下⾯是部分汇编代码,-O2汇编代码FibonacciTailRecur:.LFB12:testl %edi, %edimovl %esi, %eaxmovl %edx, %esije .L4.p2align 4,,7.L7:leal (%rax,%rsi), %edxdecl %edimovl %esi, %eaxtestl %edi, %edimovl %edx, %esijne .L7 // use jne.L4:rep ; ret-O汇编代码FibonacciTailRecur:.LFB2:pushq %rbp.LCFI0:movq %rsp, %rbp.LCFI1:subq $16, %rsp.LCFI2:movl %edi, -4(%rbp)movl %esi, -8(%rbp)movl %edx, -12(%rbp)cmpl $0, -4(%rbp)jne .L2movl -8(%rbp), %eaxmovl %eax, -16(%rbp)jmp .L1.L2:movl -12(%rbp), %eaxmovl -8(%rbp), %edxaddl %eax, %edxmovl -12(%rbp), %esimovl -4(%rbp), %edidecl %edicall FibonacciTailRecur //use callmovl %eax, -16(%rbp)movl -16(%rbp), %eaxleaveret可以看到-O2时⽤了jne命令,每次调⽤下层递归并没有申请新的栈空间,⽽是更新当前帧的局部数据,重复使⽤当前帧,所以不管有多少层尾递归调⽤都不会栈溢出,这也是使⽤尾递归的意义所在。
递归

1 递归及其实现递归是程序设计中最常用的方法之一,许多程序设计语言都提供递归调用的功能。
有些问题用递归方法求解往往使程序非常简单清晰。
栈在实现递归调用中起了关键作用。
一个直接调用自己或通过一系到的调用语句间接地调用自己的函数,称做递归函数。
直接调用自己的函数称做直接递归函数。
间接调用自己的函数称做间接递归函数。
有很多数学函数是递归定义的。
例如阶乘函数的递归定义是1 若n=0Fact(n)=n×Fact(n-1) 若n>0又例如,Fibonacci(斐波那契)数列可递归定义为0 若n=0Fib(n) = 1 若n=1Fib(n-1)+Fib(n-2) 若n>1据此可以写出实现求n 的阶乘和求Fibonacci数列中第n项的递归算法,如算法21和算法22所示。
long int fact(int n){ //求非负整数n的阶乘if(!n) return 1; //0!=1else return n*fact(n-1); //n!=n*(n-1)!}//fact算法21 求阶乘的递归算法long int fib(int n){ //求斐波那契数列中的第n个数if(n<2) return n; //f(0)=0,f(1)=1else return fib(n-1)+fib(n-2); //若n>1,f(n)=f(n-1)+f(n-2)}//fib算法22 求斐波那契数的递归算法一般地说,每一个递归函数的定义都包含两个部分。
(1) 递归基础对无需递归的规模最小的问题直接进行处理。
(2) 递归步骤将一般情况的问题简化成一个或多个规模较小的同性质的问题,递归地调用同样的方法求解这些问题,使这些问题最终简化成基础问题。
算法21的递归基础是n=0时,直接返回1(0!=1)。
一般情况下,将fact(n)简化成规模较小的问题fact(n-1),求出fact(n-1)后再与n相乘即求得了fact(n) 。
递归系统和非递归系统

一、引言计算机科学中,递归和非递归是两种常见的算法思想。
递归是指一个函数调用自身的过程,而非递归则是指不使用函数自身调用的过程。
本文将分别介绍递归系统和非递归系统,并通过实例来说明它们的优缺点和适用场景。
二、递归系统递归系统是指在函数内部调用自身的系统。
递归函数通常具有以下特点:1. 递归函数必须具有一个基本情况,即递归终止条件,否则会出现无限递归的情况。
2. 递归函数必须具有一个递归情况,即函数在递归终止条件不满足时,调用自身以达到终止条件的过程。
递归系统的优点在于它可以使复杂的问题得到简化,从而更容易理解和实现。
例如,计算斐波那契数列的第n项可以使用递归函数实现,代码如下:```int fibonacci(int n) {if (n == 0 || n == 1) {return n;} else {return fibonacci(n - 1) + fibonacci(n - 2);}}```递归系统的缺点在于它的效率不高,因为每次调用函数都需要将函数的局部变量和返回地址压入栈中,当递归层数过多时,会占用大量的内存和时间。
例如,计算斐波那契数列的第50项需要递归调用函数超过1.2亿次,耗时几乎无法计算。
三、非递归系统非递归系统是指不使用函数自身调用的系统。
非递归算法通常使用循环结构来代替递归调用,从而提高效率。
例如,计算斐波那契数列的第n项可以使用循环结构实现,代码如下:```int fibonacci(int n) {int a = 0, b = 1;for (int i = 0; i < n; i++) {int c = a + b;a = b;b = c;}return a;}```非递归系统的优点在于它的效率高,因为不需要频繁地将函数的局部变量和返回地址压入栈中。
例如,计算斐波那契数列的第50项只需要循环50次,耗时非常短。
非递归系统的缺点在于它不够直观和易于理解,因为循环结构的嵌套可能会使代码变得复杂难懂。
c语言递归函数的非递归实现

递归函数可以通过使用栈(stack)数据结构来实现非递归版本。
递归函数在每次调用时,都会将当前的状态(如局部变量和参数)压入栈中,然后返回上一级的状态。
因此,我们可以模拟这个过程,使用栈来保存中间状态,然后一步步地处理,直到栈为空。
以下是一个简单的例子,说明如何将递归函数转化为非递归函数。
假设我们有一个递归函数,用于计算阶乘:c复制代码int factorial_recursive(int n) {if (n == 0) {return1;} else {return n * factorial_recursive(n - 1);}}这个递归函数可以转化为以下的非递归函数:c复制代码#include<stdio.h>#include<stdlib.h>int factorial_iterative(int n) {int stack[n+1]; // 创建一个栈,用于保存中间状态int top = 0; // 栈顶指针int result = 1; // 初始化结果为1// 将n到1的所有数压入栈中for (int i = 1; i <= n; i++) {stack[top++] = i;}// 从栈中取出数,计算阶乘while (top > 0) {result *= stack[--top];}return result;}int main() {int n = 5;printf("Factorial of %d is %d\n", n, factorial_iterative(n));return0;}以上代码中,我们首先创建了一个栈,并将需要计算的数(从1到n)压入栈中。
然后,我们从栈中取出数,并计算阶乘。
这样,我们就模拟了递归函数的行为,但是使用了非递归的方式实现。
需要注意的是,这种方法只适用于那些可以转化为迭代形式的递归函数。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
struct
{ int vn; /*保存n值*/
int vf; /*保存fun1(n)值*/
int tag; /*标识是否求出fun1(n)值,1:未求出,0:已求出*/
} St[MaxSize];
/*定义栈*/
计算fun1(5)之值的过程如下:
将(5,*,1)进栈; while (栈不空) { if (栈顶元素未计算出栈顶元素的vf值即St[top].tag==1)
}
3. 问题的求解方法是递归的 有些问题的解法是递归的,典型的有汉诺塔(Tower
of Hanoi)问题求解:
盘片移动时必须遵守以下规则: •每次只能移动一个盘片; •盘片可以插在A,B和C中任一塔座; •不能将一个较大的盘片放在较小的盘片上。
Hanoi(n,a,b,c)
Hanoi(n-1,a,c,b); move(n,a,c); Hanoi(n-1,b,a,c)
6.1.3 递归模型
递归模型是递归算法的抽象,它反映递归问题的递 归结构,例如,前面的递归算法对应的递归模型如下:
fun(0)=1 fun(n)=n*fun(n-1)
n=0 (1) n>0 (2)
其中:第一个式子给出了递归的终止条件,我们称 之 为 递 归 出 口 ; 第 二 个 式 子 给 出 了 fun(n) 的 值 与 fun(n-1)的值之间的关系,我们称之为递归体。
其中,
(6.2)
n,i,j,m均为正整数。
sn+1是一个递归“大问题”, si,si+1,…,sn为递归“小问题”, cj,cj+1,…,cm是可以用非递归方法直接解决的问题 g是一个非递归函数,可以直接求值。
递归思路
实际上, 递归思路是把一个不能或不好直接求解 的“大问题”转化成一个或几个“小问题”来解决, 再把这些“小问题”进一步分解成更小的“小问题” 来解决,如此分解,直至每个“小问题”都可以直接解 决(此时分解到递归出口)。
对于递归数据结构,采用递归的方法编写算法既方 便又有效。例如,求一个不带头结点的单链表head的所 有data域(假设为int型)之和的递归算法:
int Sum(LinkList *head) {
if (head==NULL) return 0;
else return (head->data+Sum(head->next));
A[0]
当i=0时
f(A,i)=
MIN(f(A,i-1),A[i]) 其他情况
由此得到如下递归求解算法:
float f(float A[],int i) { float m;
if (i==0) return A[0];
else { m=f(A,i-1);
if (m>A[i]) return A[i];
但递归分解不是随意的分解,递归分解要保证“大 问题”与“小问题”相似,即求解过程与环境都相似。 并且有一个分解的终点。从而使问题可解。
求解5!的过程如下:
fun(5)
fun(5)=120
d1:fun(4)
fun(4)=24
d2:fun(3)
fun(3)=6
d3:fun(2)
fun(2)=2
d4:fun(1)
return(x);
}
6.1.2 何时使用递归 在以下三种情况下,常常要用到递归的方法。 1. 定义是递归的 2. 数据结构是递归的 3. 问题的求解方法是递归的
1. 定义是递归的
有许多数学公式、数列等的定义是递归的。这些 问题的求解过程可以将其递归定义直接转化为对应的 递归算法。
例如,求Fibonacci数列:
return(x); }
在该函数factorial (n)求解过程中,直接调用factorial (n-1)自身,所以它是一个直接递归函数。
int factorial(int n)
n=4 n=3 n=2 n=1 n=0
{ int x;
if (n==0)
x=1;
else
x=factorial (n-1)*n;
{ if (栈顶元素满足(1)式) 求出对应的St[top].vf值,并置St[top].tag=0; 表示已求出对应的函数值;
else /*栈顶元素满足(2)式*/ 将(St[top].vn-1,*,1)进栈;
} else if (栈顶元素已计算出栈顶元素的vf值即St[top].tag==0)
显然栈顶元素由次栈顶元素通过(2)式分解得到的,回过来 由栈顶元素的vf值计算出次栈顶元素的vf值并退栈; if (栈中只有一个已求出vf值的元素) 退出循环;
递归算法设计先要给出递归模型,再转换成对应的 C/C++语言函数。
递归设计的步骤如下:
(1) 对 原 问 题 f(s) 进 行 分 析 , 假 设 出 合 理 的 “ 较 小 问 题”f(s')(与数学归纳法中假设n=k-1时等式成立相似);
(2)假设f(s')是可解的,在此基础上确定f(s)的解,即给出 f(s)与f(s')之间的关系(与数学归纳法中求证n=k时等式成 立的过程相似);
返回 1
斐波那契数列的递归调用树
递归的执行过程由分解过程和求值过程两部分构成。
分解过程是“量变”过程,即原来的“大问题”在慢慢 变小,但尚未解决;遇到递归出口后,便发生了“质 变”,即原递归问题便转化成直接问题。
6.2 递归算法的设计
递归的求解的过程均有这样的特征:先将整个问题 划分为若干个子问题,通过分别求解子问题,最后获得 整个问题的解。而这些子问题具有与原问题相同的求 解方法,于是可以再将它们划分成若干个子问题,分别 求解,如此反复进行,直到不能再划分成子问题,或已经 可以求解为止。这种自上而下将问题分解、求解,再自 上而下引用、合并,求出最后解答的过程称为递归求解 过程。这是一种分而治之的算法设计方法。
例 求n!(n为正整数) 。 一般的方法:
n! = 1*2*…*(n-1)*n 递归的方法:
1,
当n 0时
n! n (n 1)!, 当n 1时
int factorial(int n) { int x;
if (n==0(n-1)*n;
St[top-1].tag=0; top--; } /*栈中只有一个已求出vf的元素时退出循环*/ if (top==0 && St[top].tag==0) break; } return(St[top].vf); }
本章小结
本章基本学习要点如下: (1)理解递归的定义和递归模型。 (2)重点掌握递归的执行过程。 (3)掌握递归设计的一般方法。 (4)掌握消除递归的基本方法。 (5)灵活运用递归算法解决一些较复杂应用问题。
(3)确定一个特定情况(如f(1)或f(0))的解,由此作为递 归出口(与数学归纳法中求证n=1时等式成立相似)。
例如,采用递归算法求实数数组A[0..n-1]中的最小值。
假设f(A,i)函数求数组元素A[0]~A[i]中的最小值。
当i=0时,有f(A,i)=A[0];
假 设 f(A,i-1) 已 求 出 , 则 f(A,i)=MIN(f(A,i-1),A[i]), 其 中 MIN()为求两个值较小值函数。因此得到如下递归模型:
} St[0].vf即为所求的fun1(n)值;
对应的非递归fun1算法如下:
int fun1(int n)
{ top++;
/*初值进栈*/
St[top].vn=n; St[top].tag=1;
while (top>-1)
/*栈不空时循环*/
{
if (St[top].tag==1) /*未计算出栈顶元素的vf值*/
第6章 递归
6.1 什么是递归 6.2 递归算法的设计 6.3 递归算法到非递归算法的转换
本章小结
6.1 什么是递归
6.1.1 递归的定义
在定义一个过程或函数时出现调用本过程或本函 数的成分,称之为递归。若调用自身,称之为直接递归。 若过程或函数p调用过程或函数q,而q又调用p,称之 为间接递归。
一般地,一个递归模型是由递归出口和递归体两部 分组成, 前者确定递归到何时结束, 后者确定递归求解 时的递推关系。
递归出口的一般格式如下:
f(s1)=m1
(6.1)
这里的s1与m1均为常量,有些递归问题可能有几个 递归出口。
递归体的一般格式如下:
f(sn+1)=g(f(si),f(si+1),…,f(sn),cj,cj+1,…,cm)
6.3.1 利用循环跳过分解过程从而消除递归
采用循环结构消除递归。求解Fibonacci数列的算法 如下:
int Fib(int n) { int i,f1,f2,f3;
if (n==1 || n==2) return(n); f1=1;f2=2; for (i=3;i<=n;i++) { f3=f1+f2;
{ if (St[top].vn==0) /*(1)式*/
{ St[top].vf=1;
St[top].tag=0;
}
else
/*(2)式*/
{ top++;
St[top].vn=St[top-1].vn-1; St[top].tag=1;
}
}
else if (St[top].tag==0) /*已计算出vf值*/ { St[top-1].vf=St[top-1].vn*St[top].vf; /*(2)式*/
f1=f2;f2=f3; } return(f3); }
6.3.2 模拟系统的运行时栈消除递归
下面讨论直接使用栈保存中间结果,从而将递归算 法转化为非递归算法的过程。