数据结构与算法—递归与非递归转换

合集下载

二叉树后序遍历的递归和非递归算法

二叉树后序遍历的递归和非递归算法

安 徽电气工 程 职 业 技术学 院学报
:薹6 M2 a r 0 c h 0

-X树后序遍历的递归和非递归算法
孙泽宇, 赵国增 , 舒云星・
( 洛阳工业高等专科学校计算机系 , 河南 洛阳 4 10 ) 703
[ 要 ] 论述了二又树后序遍历的递归算法和非递归算法, 摘 对递归算法中的工作栈 的执行过程做 了
Srcbt e t t ie { u r
● 收稿 日期 :0 5—1 0 70 . 2— 2
作者筒介: 孙泽字(97 . 吉林长春人. 17 一) 男。 洛阳工业高等专科学校计算机秉麓师。研究方向: 人工智能。 趟 目增 (97 . 河南越壁人 。 阳工业高等专科 学校计算机 秉麓师 。研究方 向: 1 一) 男。 7 洛 人工智能。
s c br 木e , r h;} t t ie lt 木 i t m te f g
后序遍历二叉树的递归算法如下 :
T p d fs u tBT o e y e e r c in d t
法及执行时栈 的变化情况 , 可设计 出较好 的非递归化算法 , 本文讨论了二叉树后序遍历的递归和非递归
算法。 2 后序遍历二叉树的递归算法
1 后序遍历左子树( ) 若左子树不为空 ) 2 后序遍历右子树( ) 若右子树不为空 ) 3 访问根结点 ( ) 若存在根结点)
二叉树数据结构如下 :
二叉树是数据结构 中最常见 的存储形式 , 在算法与数据结构中经常使用。树与森林都可以转换为 二叉树 , 而遍历算法则是二叉树最重要的操作 。所谓遍历二叉树 , 就是遵从某种次序 , 遍访二叉树 中的
所有结点, 使得每个结点被访问一次 , 而且仅一次。在遍历算法中, 递归算法是最普遍 的, 弄清 了递归算

递归 数据结构

递归 数据结构

递归数据结构
递归是一种解决问题的方法,其中一个函数通过调用自身来解决更小规模的子问题。

在数据结构方面,递归可以应用于以下几个常见的数据结构:
1. 数组:使用递归可以遍历数组中的每个元素,或者对数组进行排序、搜索等操作。

2. 链表:递归可以用于链表的反转、合并等操作。

也可以通过递归来遍历链表中的每个节点。

3. 树:树是递归定义的数据结构,因此递归在树的操作中非常常见。

递归可以用于树的遍历(如前序、中序、后序遍历)、搜索、插入、删除等操作。

4. 图:递归可以应用于图的遍历算法,如深度优先搜索(DFS)和广度优先搜索(BFS)。

在递归的实现中,通常需要定义递归的基本情况(即递归的终止条件)和递归的递推关系(即如何将原问题转化为更小规模的子问题)。

同时,需要注意递归的限制,避免出现无限递归的情况。

总而言之,递归在解决数据结构相关问题时非常有用,可以简化问题的复杂性,但也需要注意递归的边界条件和限制。

数据结构与算法分析论文(递归的讨论)

数据结构与算法分析论文(递归的讨论)

数据结构论文——递归算法的讨论所谓递归算法是把问题转化为规模缩小了的同类问题的子问题。

然后递归调用函数(或过程)来表示问题的解。

一个过程(或函数)直接或间接调用自己本身,这种过程(或函数)叫递归过程(或函数)。

递归过程一般通过函数或子过程来实现。

递归方法:在函数或子过程的内部,直接或者间接地调用自己的算法。

递归算法是一种直接或者间接地调用自身算法的过程。

在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解。

递归算法解决问题的特点:(1) 递归就是在过程或函数里调用自身。

(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。

(3) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。

(4) 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。

递归次数过多容易造成栈溢出等。

所以一般不提倡用递归算法设计程序。

下面就让我们结合例子详细讨论一下递归算法。

一、递归算法的原理递归算法简单的说就是在函数中调用函数自身,不断调用,直到满足函数得出计算结果(某个条件)。

因为其需要不断循环的调用自身,所以称为递归调用。

递归的原理,其实就是一个栈(stack), 比如求5的阶乘,要知道5的阶乘,就要知道4的阶乘,4又要是到3的,以此类推,所以递归式就先把5的阶乘表示入栈, 在把4的入栈,直到最后一个,之后呢在从1开始出栈, 看起来很麻烦,确实很麻烦,他的好处就是写起代码来,十分的快,而且代码简洁,其他就没什么好处了,运行效率出奇的慢。

还有一个十分形象的例子:从前有座山,山里有个庙,庙里有个老和尚正在讲故事:从前有座山,山里有个庙,庙里有个老和尚正在讲故事:从前有座山,山里有个庙,庙里有个老和尚正在讲故事……如此循环往复到最终的要求。

递归分为2种,直接递归和间接递归。

直接递归,比如方法A内部调用方法A自身。

间接递归,比如方法A内部调用方法B,方法B内部调用方法C,方法C 内部调用方法A。

将数组转换成树形的方法

将数组转换成树形的方法

将数组转换成树形的方法将数组转换成树形结构是一种常见的操作,这在数据处理和算法设计中经常用到。

本文将介绍一种有效的方法,帮助读者理解并掌握这个过程。

一、什么是树形结构?树形结构是一种非线性的数据结构,由多个节点(node)组成,这些节点通过边(edge)相连。

每个节点可以有零个或多个子节点,而根节点是这颗树的起点。

树形结构常常用来表示层次关系或者组织结构。

二、数组与树的关系在计算机中,数组是一种线性的数据结构,由一组连续的内存单元组成。

数组的元素可以通过索引(index)来访问,索引从0开始递增。

数组和树之间的转换可以看作是将线性结构转换成非线性结构的过程。

三、转换方法将数组转换成树形结构的方法可以分为递归和非递归两种。

1. 递归方法递归方法是一种简洁而直观的转换方式。

首先,我们需要定义一个递归函数,用来创建节点并连接它们的子节点。

递归函数的输入参数通常包括数组、起始索引和结束索引。

在函数的内部,我们可以通过判断起始索引和结束索引的关系,来确定递归的终止条件。

当起始索引等于结束索引时,说明已经到达叶子节点,不需要再创建子节点了。

否则,我们可以计算出数组中间位置的索引,并根据该索引创建当前节点,并递归调用函数创建左子树和右子树。

2. 非递归方法非递归方法是一种迭代的转换方式。

我们可以借助一个栈(stack)来保存节点和索引的信息。

开始时,我们将根节点入栈,并初始化起始索引和结束索引。

然后,我们进入一个循环,直到栈为空为止。

在循环中,我们首先从栈中弹出一个节点,并获取它的索引。

然后,我们根据索引计算出左子树和右子树的起始索引和结束索引,并创建相应的节点。

最后,我们将新创建的节点入栈,并更新起始索引和结束索引。

通过这种方式,我们可以逐步构建出树形结构。

四、示例分析为了更好地理解数组到树的转换过程,我们以一个具体的示例来说明。

假设有一个数组 arr = [1, 2, 3, 4, 5, 6, 7],我们希望将其转换成树形结构。

程序设计算法探讨——递归调用与非递归调用

程序设计算法探讨——递归调用与非递归调用
于 递 归 过 程 结 构 清 晰 , 序 易 读 , 用 递 归 给 用 户 编 制 程 序 程 利
e s e u n n*f n 1 ) le r t r ( ( 一 )
e d; n
和 调 试 程 序带 来 很 大 方 便 。 而 , 一 般情 况下 , 然 在 一个 递 归算
法 的 时空 性 能 相对 较 差 , 究 递 归 消除 有 助 于透 彻 理 解 递 归 研
数 , 决著名的汉诺塔问题、 波那切数列等 。 解 斐 其 二 , 的数 据 结 构 , 有 比如 二 叉 树 , 义 表 , 广 图等 , 构 本 结 身 的 特 征 就决 定 了它 们 的操 作 可 递 归 进 行 。
其 三 , 些 问题 , 本 身 没 有 明 显 的递 归 结 构 , 用 递 归 有 虽 但
V o1 2 N o. .0 2
第2卷 O
第2 期
程序 设 计 算 法探 讨
递 归调 用 与非 递归 调用
徐 振 华
( 东胜 利 职 业 学 院 微 机 教 研 室 . 东 东 营 2 7 9 ) 山 山 5 0 7
[ 摘 要] 递归调用是程序设计中一个非常重要的方法。本文对程序算法的递归调用及递归调用与非递归调用的转
化从基本 定义、 实现 过 程 及 转 化 方 法 等 几 个 方 面 进 行 了理 论探 讨 , 结合 实例 做 了进 一 步应 用 分 析 。 此 法 简单 直 观 , 构 清 并 结
晰 . 计 算 机 的 执 行 过 程 比 较 复 杂 , 空 性 能相 对较 差 。若 在 程 序 中消 除 递 归 调 用 , 其运 行 时 间 可 大 为节 省 。 但 时 则
递归是一个重要 的概念 , 同时 也 是 一 种 重 要 的程 序 设 计 方法 。 简单 地说 , 果 在 一 个 函 数 、 程 或数 据 结 构 的定 义 中 如 过 又应 用 了 它 自 身 , 么 这 个 函数 、 程 或 数 据 结 构 称 为 是 递 那 过

武汉大学数据结构考试题(附答案)

武汉大学数据结构考试题(附答案)

武汉大学数据结构考试题(附答案)编辑整理:尊敬的读者朋友们:这里是精品文档编辑中心,本文档内容是由我和我的同事精心编辑整理后发布的,发布之前我们对文中内容进行仔细校对,但是难免会有疏漏的地方,但是任然希望(武汉大学数据结构考试题(附答案))的内容能够给您的工作和学习带来便利。

同时也真诚的希望收到您的建议和反馈,这将是我们进步的源泉,前进的动力。

本文可编辑可修改,如果觉得对您有帮助请收藏以便随时查阅,最后祝您生活愉快业绩进步,以下为武汉大学数据结构考试题(附答案)的全部内容。

1. 下面程序段的执行次数为( A )for(i=0;i<n-1;i++)for(j=n;j>i;j--)state;A. n(n+2)2 B 。

(n-1)(n+2)2 C。

n(n+1)2 D. (n-1)(n+2)2。

一个向量第一个元素的存储地址是100,每个元素的长度为2,则第5个元素的地址是( B )A. 110 B 。

108 C. 100 D. 1203. 一个栈的入栈序列是a,b,c,d,e,则栈的不可能的输出序列是( C )A。

edcbaB 。

decba C. dceab D. abcde4。

循环队列用数组A[0,m-1]存放其元素值,已知其头尾指针分别是front和rear,则当前队列中的元素个数是( D )A. (rear-front+m)%m B .read—front+1C. read-front—1 D. read—front5.不带头结点的单链表head为空的判定条件是( A )A. head=NULL B .head—next=NULLC. head-next=head D。

head!=NULL6.在一个单链表中,若p所指的结点不是最后结点,在p之后插入s所指结点,则执行(B)A。

s-next=p;p—next=s; B .s-next=p—next;p—next=s; C。

s—next=p—next;p=s; D. p—next=s;s-next=p;7. 从一个具有n个结点的单链表中查找其值等于x结点时,在查找成功的情况下,需平均比较多少个结点( D )A. n B .n2 C。

数据结构课后习题及答案

数据结构课后习题及答案

填空题(10 *1' = 10’)一、概念题2。

2。

当对一个线性表经常进行的是插入和删除操作时,采用链式存储结构为宜。

2.3.当对一个线性表经常进行的是存取操作,而很少进行插入和删除操作时,最好采用顺序存储结构。

2。

6。

带头结点的单链表L中只有一个元素结点的条件是L—〉Next-〉Next==Null。

3.6.循环队列的引入,目的是为了克服假溢出。

4.2。

长度为0的字符串称为空串。

4.5.组成串的数据元素只能是字符。

4.8。

设T和P是两个给定的串,在T中寻找等于P的子串的过程称为模式匹配,又称P为模式。

7。

2。

为了实现图的广度优先搜索,除一个标志数组标志已访问的图的结点外,还需要队列存放被访问的结点实现遍历。

5。

7。

广义表的深度是广义表中括号的重数7。

8.有向图G可拓扑排序的判别条件是有无回路.7。

9。

若要求一个稠密图的最小生成树,最好用Prim算法求解.8。

8。

直接定址法法构造的哈希函数肯定不会发生冲突。

9.2。

排序算法所花费的时间,通常用在数据的比较和交换两大操作.1.1.通常从正确性﹑可读性﹑健壮性﹑时空效率等几个方面评价算法的(包括程序)的质量.1。

2.对于给定的n元素,可以构造出的逻辑结构有集合关系﹑线性关系树形关系﹑图状关系四种。

1.3。

存储结构主要有顺序存储﹑链式存储﹑索引存储﹑散列存储四种。

1.4。

抽象数据类型的定义仅取决于它的一组逻辑特性,而与存储结构无关,即不论其内部结构如何变化,只要它的数学特性不变,都不影响其外部使用。

1.5.一个算法具有五大特性:有穷性﹑确定性﹑可行性,有零个或多个输入﹑有一个或多个输入。

2.8.在双向链表结构中,若要求在p指针所指的结点之前插入指针为s所指的结点,则需执行下列语句:s->prior= p->prior; s—〉next= p; p-〉prior—next= s; p-〉prior= s;。

2.9。

在单链表中设置头结点的作用是不管单链表是否为空表,头结点的指针均不空,并使得对单链表的操作(如插入和删除)在各种情况下统一.3。

数据库系统l试题库及答案第3章栈与队列

数据库系统l试题库及答案第3章栈与队列

第3章栈和队列3.1栈一、填空题1. 线性表、栈和队列都是________ 结构,可以在线性表的__________ 位置插入和删除元素;对于栈只能___________插入和删除元素;对于队列只在 ____________ 插入元素,并且只在____________ 删除元素。

2. 栈是一种特殊的线性表,允许插入和删除运算的一端称为____________ 。

不允许插入和删除运算的一端称为_________ 。

3. 向栈中压入元素的操作是先____________ ,后 _______ 。

4. 从栈中弹出元素的操作是先____________ ,后 ________ 。

二、选择题:1. ()栈中元素的进出原则是()。

A.先进先出 B .后进先出C .栈空则进D .栈满则出2. ()若已知一个栈的入栈序列是1 , 2, 3,…,n,其输出序列为pl, p2, p3,…,pn,若p仁n,贝U pi为()。

A. i B . n=i C . n-i+1 D .不确定3. ()判定一个栈ST (最多元素个数为m0)为空的条件是()。

A. ST->top<>0 B . ST->top=0 C . ST->top<>mO D . ST->top=mO4. ()有六个元素1,2,3,4,5,6 的顺序进栈,问下列哪一个不是合法的出栈序列?()A. 1,2,3,4,5,6B. 5,4,3,2,1,6C. 4,3,2,1,5,6D. 6,5,4,3,1,25. ()将递归算法转换成非递归算法时,通常要借助的数据结构是()。

A.线性表B. 栈C. 队列D. 树6. ()若栈采用顺序存储方式存储,现两栈共享空间V[1..m] , top[i]代表第i个栈(i =1,2)栈顶,栈1的底在v[1],栈2的底在V[m],则栈满的条件是()。

A. |top[2]-top[1]|=0B. top[1]+1=top[2]C. top[1]+top[2]=mD. top[1]=top[2]7. ()一个递归算法必须包括()。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

递归与非递归转换的基础知识是能够正确理解三种树的遍历方法:前序,中序和后序,第一篇就是关于这三种遍历方法的递归和非递归算法。

一、为什么要学习递归与非递归的转换的实现方法?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>。

}pop(S,p>。

if(!stackempty(S>> { /* 向右走一步 */pop(S,p>。

push(S,p->rchild>。

}}}2>中序遍历a>递归方式void inorder_recursive(Bitree T> /* 中序遍历二叉树的递归算法 */ {if (T> {inorder_recursive(T->lchild>。

/* 访问左子树 */visit(T>。

/* 访问当前结点 */inorder_recursive(T->rchild>。

/* 访问右子树 */}b>非递归方式void inorder_nonrecursive(Bitree T>{initstack(S>。

/* 初始化栈 */push(S,T>。

/* 根指针入栈 */while (!stackempty(S>> {while (gettop(S, p> && p> /* 向左走到尽头 */push(S,p->lchild>。

pop(S,p>。

/* 空指针退栈 */if (!stackempty(S>> {pop(S,p>。

visit(p>。

/* 访问当前结点 */push(S,p->rchild>。

/* 向右走一步 */}}}3>后序遍历a>递归方式void postorder_recursive(Bitree T> /* 中序遍历二叉树的递归算法 */ {if (T> {postorder_recursive(T->lchild>。

/* 访问左子树 */postorder_recursive(T->rchild>。

/* 访问右子树 */visit(T>。

/* 访问当前结点 */}}b>非递归方式typedef struct {BTNode* ptr。

enum {0,1,2} mark。

} PMType。

/* 有mark域的结点指针类型 */void postorder_nonrecursive(BiTree T> /* 后续遍历二叉树的非递归算法*/{PMType a。

initstack(S>。

/* S的元素为PMType类型 */push (S,{T,0}>。

/* 根结点入栈 */while(!stackempty(S>> {pop(S,a>。

switch(a,mark>{case 0:push(S,{a.ptr,1}>。

/* 修改mark域 */if(a.ptr->lchild>push(S,{a,ptr->lchild,0}>。

/* 访问左子树 */break。

case 1:push(S,{a,pt,2}>。

/* 修改mark域 */if(a.ptr->rchild>push(S,{a.ptr->rchild,0}>。

/* 访问右子树 */break。

case 2:visit(a.ptr>。

/* 访问结点 */}}}三、实现递归与非递归的换转原理和例子一)原理分析通常,一个函数在调用另一个函数之前,要作如下的事情:a>将实在参数,返回地址等信息传递给被调用函数保存。

b>为被调用函数的局部变量分配存储区。

c>将控制转移到被调函数的入口。

从被调用函数返回调用函数之前,也要做三件事情:a>保存被调函数的计算结果。

b>释放被调函数的数据区。

c>依照被调函数保存的返回地址将控制转移到调用函数。

所有的这些,不论是变量还是地址,本质上来说都是”数据”,都是保存在系统所分配的栈中的。

ok,到这里已经解决了第一个问题:递归调用时数据都是保存在栈中的,有多少个数据需要保存就要设置多少个栈,而且最重要的一点是:控制所有这些栈的栈顶指针都是相同的,否则无法实现同步。

下面来解决第二个问题:在非递归中,程序如何知道到底要转移到哪个部分继续执行?回到上面说的树的三种遍历方式,抽象出来只有三种操作:访问当前结点,访问左子树,访问右子树。

这三种操作的顺序不同,遍历方式也不同。

如果我们再抽象一点,对这三种操作再进行一个概括,可以得到:a>访问当前结点:对目前的数据进行一些处理。

b>访问左子树:变换当前的数据以进行下一次处理。

c>访问右子树:再次变换当前的数据以进行下一次处理(与访问左子树所不同的方式>。

下面以先序遍历来说明:void preorder_recursive(Bitree T> /* 先序遍历二叉树的递归算法 */{if (T> {visit(T>。

/* 访问当前结点 */preorder_recursive(T->lchild>。

/* 访问左子树 */preorder_recursive(T->rchild>。

/* 访问右子树 */}}visit(T>这个操作就是对当前数据进行的处理, preorder_recursive(T->lchil d>就是把当前数据变换为它的左子树,访问右子树的操作可以同样理解了。

现在回到我们提出的第二个问题:如何确定转移到哪里继续执行?关键在于一下三个地方:a>确定对当前数据的访问顺序,简单一点说就是确定这个递归程序可以转换为哪种方式遍历的树结构。

b>确定这个递归函数转换为递归调用树时的分支是如何划分的,即确定什么是这个递归调用树的”左子树”和”右子树”c>确定这个递归调用树何时返回,即确定什么结点是这个递归调用树的”叶子结点”。

二)两个例子好了上面的理论知识已经足够了,下面让我们看看几个例子,结合例子加深我们对问题的认识。

即使上面的理论你没有完全明白,不要气馁,对事物的认识总是曲折的,多看多想你一定可以明白(事实上我也是花了两个星期的时间才弄得比较明白得>。

1>例1:f(n> = n + 1。

(n <2>= f[n/2] + f[n/4](n >= 2>。

这个例子相对简单一些,递归程序如下:int f_recursive(int n>{int u1, u2, f。

if (n < 2>f = n + 1。

else {u1 = f_recursive((int>(n/2>>。

u2 = f_recursive((int>(n/4>>。

f = u1 * u2。

}return f。

}下面按照我们上面说的,确定好递归调用树的结构,这一步是最重要的。

首先,什么是叶子结点,我们看到当n < 2时f = n + 1,这就是返回的语句,有人问为什么不是f = u1 * u2,这也是一个返回的语句呀?答案是:这条语句是在u1 = exmp1((int> (n/2>>和u2 = exmp1((int>(n/4>>之后执行的,是这两条语句的父结点。

其次,什么是当前结点,由上面的分析,f = u1 * u2即是父结点。

然后,顺理成章的u1 = exmp1((int>(n/2>>和u2 = exmp1((int>(n/4>>就分别是左子树和右子树了。

最后,我们可以看到,这个递归函数可以表示成后序遍历的二叉调用树。

好了,树的情况分析到这里,下面来分析一下栈的情况,看看我们要把什么数据保存在栈中:非递归程序中我们已经看到了要加入一个标志域,因此在栈中要保存这个标志域。

另外,u1,u2和每次调用递归函数时的n/2和n/4参数都要保存,这样就要分别有三个栈分别保存:标志域,返回量和参数,不过我们可以做一个优化,因为在向上一层返回的时候,参数已经没有用了,而返回量也只有在向上返回时才用到,因此可以把这两个栈合为一个栈。

如果对于上面的分析你没有明白,建议你根据这个递归函数写出它的递归栈的变化情况以加深理解,再次重申一点:前期对树结构和栈的分析是最重要的,如果你的程序出错,那么请返回到这一步来再次分析,最好把递归调用树和栈的变化情况都画出来,并且结合一些简单的参数来人工分析你的算法到底出错在哪里。

ok,下面给出我花了两天功夫想出来的非递归程序(再次提醒你不要气馁,大家都是这么过来的>。

代码:int f_nonrecursive(int n>{int stack[20], flag[20],cp。

相关文档
最新文档