栈论 : 递归与栈式访问,如何用栈实现所有递归操作(幼儿园题目篇)
上一篇 :
栈论 : 递归与栈式访问,如何用栈实现所有递归操作(函数调用底层篇)
2.用基础知识实现递归转栈式访问
基于以上几点,我们怎么把所有的递归都用栈这个数据结构实现呢?
想必聪明的你已经有想法了吧!
例题 : (请先自己思考尝试一下怎么实现)
先给出二叉树节点定义 :
typedef struct BiTNode{
char data;
BiTNode* lchild,*rchild;
} *BiTree;
访问二叉树使用的函数 :
void visit(BiTNode * node)
如果觉得上述节点定义眼熟,那我们俩应该都是供液大学的。
题1.难度等级: C
使用栈实现二叉树的先序遍历
函数头:
void preOrderRead(BiTree tree);
题2.难度等级: B
使用栈实现二叉树的后续遍历
void postOrderRead(BiTree tree);
题3.难度等级: A
二叉树的节点含有成员变量M
找出二叉树中成员变量M为a 和 b 的节点的最小祖先节点(假设a b 只出现一次)
BiTNode* findNearestAncestor(BiTree tree, char a, char b);
题目1为例,思路开导与解析 :
首先我们需要写出大概的使用递归实现功能的代码。
题1:
void preOrderRead(BiTree tree){
if(tree == NULL){
return;
}
visit(tree);//3
preOrderRead(tree -> lchild); //1
preOrderRead(tree -> rchild);//2
}
怎么转换成栈实现呢?
从之前我们分析中知道,对函数的调用实际上是创建栈帧的过程,那么上图1,2处我们调用了两次函数,那么在这两处我们应该都要
用创建栈帧来代替。
问题是创建的栈帧里面应该包含什么内容呢?
应该包含这个栈帧创建到销毁过程中所有需要的信息。而这里的信息可能不是直接获得的,例如可能我们的栈帧中包含了一个指向父栈帧的指针,那么我们就可以和父栈帧
通信,而无需要把父栈帧中的某些变量之类的信息冗杂地包含到栈帧里来。
严谨一点地说,栈帧应该包含本栈帧创建到销毁过程中需要的所有信息的 “来源或者信息本身”。
还有更重要的一点,递归函数的方法体只有一个,也就是说,对说有的栈帧都要进行同一个操作,无论这个栈帧包含的信息有多么不一样!
所以,方法中对栈帧的处理至关重要,他将作用于所有栈帧。
要能够根据栈帧内包含的信息对信息不同的栈帧做出合理的操作。
返回我们的题目1。除去1和2这两个创建栈帧的过程。如果把当前方法的调用想成一个栈帧,那么我们在栈帧里需要执行的操作只是判断本栈帧的节点是否为空,不空就读取,仅此而已。
对应的,设计我们的函数实现.
在这里,我们把栈的元素直接设计为节点,因为节点的信息已经够我们完成所有操作(只有visit操作而已);
1.如果把栈帧的入栈想成函数调用,出栈想成函数返回,那么当栈为空的时候,函数调用就结束了。于是有了下面1处的判断栈是否是空的
2.你可能会问:子函数都没调用完,2处怎么就把父函数的栈帧出栈了呢?因为如果我们在把子函数栈帧入栈(调用子函数)前将父函数的所有操作都做了,并且子函数的栈帧不需要和父函数栈帧通信的话,那么父函数的栈帧没有存在在栈中的意义了,因为该执行的都执行完了,子函数也不需要他,子函数在栈中的顺序也不会变,好一可怜的老父亲。
在下面需要对栈帧做的所有操作只有visit,也就是访问他的节点,子函数栈帧入栈前(调用子函数)就可以把父函数的所有操作在3处完成了,没有其他操作要等待子函数栈帧出栈(返回)接着做,而且子函数的栈帧已经包含所有操作需要的信息了(BiTree),所以2处父栈帧直接出栈。
4,5两处子函数栈帧入栈,表示父函数递归调用子函数。注意要放右孩子 rchild先,因为栈是先入后访问,而且左孩子总是先于右孩子访问
void preOrderRead(BiTree tree){
Stack stack;
init(stack);
stack.push(tree);
while( ! empty(stack)){ // 1
BiTNode* node = stack.pop(); // 2
if(node == NULL){
continue;
}
visit(node); // 3
stack.push(tree -> rchild); // 4
stack.push(tree -> lchild); // 5
}
}
下一篇 :
栈论 : 递归与栈式访问,如何用栈实现所有递归操作(幼儿园题目篇,题目2)
护眼绿: 没人看的结语: 首先很感谢你看到这里,辛苦了。 文章中某些地方可能不正确或不准确,代码也可能不够高效可读,希望读者能够帮忙指正,共同学习进步。
- 我这么玩Web Api(一)
- 1624: [Usaco2008 Open] Clear And Present Danger 寻宝之路
- 点双连通分量与割点
- 1648: [Usaco2006 Dec]Cow Picnic 奶牛野餐
- 1641: [Usaco2007 Nov]Cow Hurdles 奶牛跨栏
- 1668: [Usaco2006 Oct]Cow Pie Treasures 馅饼里的财富
- 2463: [中山市选2009]谁能赢呢?
- 2748: [HAOI2012]音量调节
- 2697: 特技飞行
- 我这么玩Web Api(二)
- 1296: [SCOI2009]粉刷匠
- 1293: [SCOI2009]生日礼物
- 记一次线程池调优经历
- JavaScript对象
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- centos6搭建gitlab的方法步骤
- Linxu服务器上安装JDK 详细步骤
- linux 不改变目录结构移动 home 目录到新分区的操作方法
- ubuntu14.04安装opencv3.0.0的操作方法
- Linux中让alias设置永久生效的方法详解
- Centos系统下“无法打开并写入文件”问题的解决
- 如何在Linux下设置录音笔时间
- Linux下ZooKeeper分布式集群安装教程
- CentOS 6.5中利用yum搭建LNMP环境的步骤详解
- Linux下Kafka分布式集群安装教程
- Centos下升级Python及Mongodb驱动安装问题
- centOS6中使用crontab定时运行执行jar程序的脚本
- 基于cobbler 实现自动安装linux系统
- Polysh命令实现多日志查询的方法示例
- linux中启动tomcat后浏览器无法访问的解决方法