栈论 : 递归与栈式访问,如何用栈实现所有递归操作(幼儿园题目篇)
上一篇 :
栈论 : 递归与栈式访问,如何用栈实现所有递归操作(函数调用底层篇)
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)
护眼绿: 没人看的结语: 首先很感谢你看到这里,辛苦了。 文章中某些地方可能不正确或不准确,代码也可能不够高效可读,希望读者能够帮忙指正,共同学习进步。
- 16.4 配置Tomcat监听80端口
- JDK容器学习之Queue:LinkedBlockingQueue
- Linux基础(day59)
- 16.3 安装Tomcat
- 16.2 安装jdk
- UITabBarController实现Tab切换
- React Native库版本升级与降级
- Java并发学习之Volatile及内存模型探究
- Java并发学习之CountDownLatch实现原理及使用姿势
- Linux基础(day58)
- 携程Android App插件化和动态加载实践
- 15.5 使用pure-ftpd搭建ftp服务
- JDK容器学习之Queue: ArrayBlockingQueue
- 手机APP安装包缩减方案
- 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 数组属性和方法
- (数据科学学习手札90)Python+Kepler.gl轻松制作时间轮播地图
- WEB前端安全自查和加固
- 硬核!IDEA 中使用 Git 完整入门教程!
- 神器你值得拥有——CoolFormat代码一键自动格式化工具,支持Verilog
- 一个简单现实案例挑战 PowerBI 水平测试 - 深度解析
- [译]使用DOT语言和GraphvizOnline来可视化你的ASP.NETCore3.0终结点01
- 探索 React 内核:深入 Fiber 架构和协调算法
- [经验]使用Keil MDK+Jlink-OB下载失败的解决办法
- ESP8266两种工作模式数据传输测试
- 『真香警告』这33个超级好用的CSS选择器,你可能见都没见过。
- [踩坑]STM32外部8M晶体不起振会有什么现象?
- 干掉 GuavaCache:Caffeine 才是本地缓存的王
- 美团数据怎么爬,看看这个文章吧!
- [硬件]关于SPI Flash那些你不知道的事儿
- 「新特性」Spring Boot 全局懒加载机制了解一下