二叉树中序遍历,先序遍历,后序遍历(递归栈,非递归栈,Morris Traversal)
例题
- 中序遍历94. Binary Tree Inorder Traversal
- 先序遍历144. Binary Tree Preorder Traversal
- 后序遍历145. Binary Tree Postorder Traversal
递归栈
递归函数栈的方法很基础,写法也很简单,三种遍历方式之间只需要改变一行代码的位置即可
中序遍历
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void inorder(TreeNode* root, vector<int>& v){
if(root != nullptr) {
inorder(root->left, v);
v.push_back(root->val); // 改变位置的代码
inorder(root->right, v);
}
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> v;
inorder(root, v);
return v;
}
};
先序遍历
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void inorder(TreeNode* root, vector<int>& v){
if(root != nullptr) {
v.push_back(root->val);
inorder(root->left, v);
inorder(root->right, v);
}
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> v;
inorder(root, v);
return v;
}
};
后序遍历
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void inorder(TreeNode* root, vector<int>& v){
if(root != nullptr) {
inorder(root->left, v);
inorder(root->right, v);
v.push_back(root->val);
}
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> v;
inorder(root, v);
return v;
}
};
非递归栈
当树的深度过大时,函数栈可能会溢出,这时候需要我们使用数据结构中的栈,来模拟节点在栈中的压入和弹出
中序遍历
cur指针时刻指向需要处理的节点
如果当前节点不为空,则压入栈中,并改变cur指针指向其左节点
如果当前节点为空,也代表空节点的中序遍历自动完成,无需压栈,这时候需要弹出栈顶的节点,保存栈顶节点的值,并更改cur指向其右子树,以完成右子树的中序遍历
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> v;
stack<TreeNode*> s;
TreeNode* cur = root;
while(cur || !s.empty()){
if(cur){
s.push(cur);
cur = cur->left;
} else {
cur = s.top();
s.pop();
v.push_back(cur->val);
cur = cur->right;
}
}
return v;
}
};
先序遍历
先序遍历与中序遍历代码相比只改变了保存节点的值的代码的位置,当先访问根的时候就记录节点,而不是等左子树遍历完,弹出根节点的时候再记录
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> v;
stack<TreeNode*> s;
TreeNode* cur = root;
while(cur || !s.empty()){
if(cur){
v.push_back(cur->val);
s.push(cur);
cur = cur->left;
} else {
cur = s.top();
s.pop();
cur = cur->right;
}
}
return v;
}
};
后序遍历
因为后序遍历的压栈顺序是左-右-根,由于先遍历完左子树,然后遍历完右子树,然后才能处理当前节点,为了和之前的代码的结构保持一致,我们可以反向处理,也就是按根-右-左的顺序压栈,
结果反向输出即可
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> v;
stack<TreeNode*> s;
TreeNode* cur = root;
while(cur || !s.empty()){
if(cur){
v.push_back(cur->val);
s.push(cur);
cur = cur->right;
} else {
cur = s.top();
s.pop();
cur = cur->left;
}
}
reverse(v.begin(), v.end()); // 反向输出结果
return v;
}
};
Morris Traversal
线索二叉树,O(n)时间,常数空间
Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)
就是当前节点的中序遍历前驱节点,如果此前驱节点的右指针为空,则将此前驱节点的右指针指向当前节点
当寻找当前节点的中序遍历前驱节点时,发现能循环到自己,说明左子树已经遍历完,需要遍历右子树
中序遍历
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
TreeNode* cur, *prev;
vector<int> v;
cur = root;
prev = nullptr;
while(cur){
if(cur->left == nullptr){
v.push_back(cur->val);
cur = cur->right;
} else {
prev = cur->left;
while(prev->right != nullptr && prev->right != cur)
prev = prev->right;
if(prev->right == nullptr){
prev->right = cur;
cur = cur->left;
} else {
v.push_back(cur->val);
prev->right = nullptr;
cur = cur->right;
}
}
}
return v;
}
};
先序遍历
同样先序遍历与中序遍历相比也只需要改变一行代码的位置
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
TreeNode* cur, *prev;
vector<int> v;
cur = root;
prev = nullptr;
while(cur){
if(cur->left == nullptr){
v.push_back(cur->val);
cur = cur->right;
} else {
prev = cur->left;
while(prev->right != nullptr && prev->right != cur)
prev = prev->right;
if(prev->right == nullptr){
v.push_back(cur->val);
prev->right = cur;
cur = cur->left;
} else {
prev->right = nullptr;
cur = cur->right;
}
}
}
return v;
}
};
后序遍历
后序遍历需要反向输出cur->left到cur的中序前驱结点之间的路径
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void reverse_traverse_reverse(TreeNode* cur, vector<int>& v){
TreeNode* prev = nullptr;
while(cur){
auto right = cur->right;
cur->right = prev;
prev = cur;
cur = right;
}
cur = prev;
prev = nullptr;
while(cur){
auto right = cur->right;
cur->right = prev;
v.push_back(cur->val);
prev = cur;
cur = right;
}
}
vector<int> postorderTraversal(TreeNode* root) {
TreeNode* cur, *prev;
vector<int> v;
cur = new TreeNode(-1);
prev = nullptr;
cur->left = root;
while(cur){
if(cur->left == nullptr){
cur = cur->right;
} else {
prev = cur->left;
while(prev->right != nullptr && prev->right != cur)
prev = prev->right;
if(prev->right == nullptr){
prev->right = cur;
cur = cur->left;
} else {
prev->right = nullptr;
reverse_traverse_reverse(cur->left, v);
cur = cur->right;
}
}
}
return v;
}
};
总结
三种遍历方式中,后序遍历的处理比较麻烦,但是无论是使用递归栈,非递归栈还是Morris Traversal,代码的结构都是一样的,中序和前序甚至只有一行代码位置的差别
使用栈的版本中,最坏空间复杂度为O(n)链型,平均空间复杂度为O(lgn)
Morris Traversal空间复杂度为O(1)
二叉树中序遍历,先序遍历,后序遍历(递归栈,非递归栈,Morris Traversal)的更多相关文章
- 二叉树前中后/层次遍历的递归与非递归形式(c++)
/* 二叉树前中后/层次遍历的递归与非递归形式 */ //*************** void preOrder1(BinaryTreeNode* pRoot) { if(pRoot==NULL) ...
- 数据结构二叉树的递归与非递归遍历之java,javascript,php实现可编译(1)java
前一段时间,学习数据结构的各种算法,概念不难理解,只是被C++的指针给弄的犯糊涂,于是用java,web,javascript,分别去实现数据结构的各种算法. 二叉树的遍历,本分享只是以二叉树中的先序 ...
- C实现二叉树(模块化集成,遍历的递归与非递归实现)
C实现二叉树模块化集成 实验源码介绍(源代码的总体介绍):header.h : 头文件链栈,循环队列,二叉树的结构声明和相关函数的声明.LinkStack.c : 链栈的相关操作函数定义.Queue. ...
- 二叉树3种递归和非递归遍历(Java)
import java.util.Stack; //二叉树3种递归和非递归遍历(Java) public class Traverse { /******************一二进制树的定义*** ...
- JAVA递归、非递归遍历二叉树(转)
原文链接: JAVA递归.非递归遍历二叉树 import java.util.Stack; import java.util.HashMap; public class BinTree { priva ...
- 二叉树的创建、遍历(递归和非递归实现)、交换左右子数、求高度(c++实现)
要求:以左右孩子表示法实现链式方式存储的二叉树(lson—rson),以菜单方式设计并完成功能任务:建立并存储树.输出前序遍历结果.输出中序遍历结果.输出后序遍历结果.交换左右子树.统计高度,其中对于 ...
- JAVA递归、非递归遍历二叉树
前序遍历:1.访问根节点 2.前序遍历左子树 3.前序遍历右子树 中序遍历:1.中序遍历左子树 2.访问根节点 3.中序遍历右子树 后序遍历:1.后序遍历左子树 2.后序遍历右子树 3.访问根节点-- ...
- 基于Java的二叉树的三种遍历方式的递归与非递归实现
二叉树的遍历方式包括前序遍历.中序遍历和后序遍历,其实现方式包括递归实现和非递归实现. 前序遍历:根节点 | 左子树 | 右子树 中序遍历:左子树 | 根节点 | 右子树 后序遍历:左子树 | 右子树 ...
- 数据结构-树以及深度、广度优先遍历(递归和非递归,python实现)
前面我们介绍了队列.堆栈.链表,你亲自动手实践了吗?今天我们来到了树的部分,树在数据结构中是非常重要的一部分,树的应用有很多很多,树的种类也有很多很多,今天我们就先来创建一个普通的树.其他各种各样的树 ...
- 二叉树之AVL树的平衡实现(递归与非递归)
这篇文章用来复习AVL的平衡操作,分别会介绍其旋转操作的递归与非递归实现,但是最终带有插入示例的版本会以递归呈现. 下面这张图绘制了需要旋转操作的8种情况.(我要给做这张图的兄弟一个赞)后面会给出这八 ...
随机推荐
- hibernate必须的包下载以及各个包的作用
hibernate必须的包(点击下载) 对应的入门级代码例子(点击下载) hibernate3.jar hibernate核心包 commons-collections-2.1.1.jar 集合包 ...
- VS Code 中的LaTeX自动保存问题
最近更新了VS Code,编辑LaTeX文档是出现了一个很恼人的问题:只要文档一改动,立马就Build,在更新之前是没有这个问题的. 解决方案如下:在设置里找到Auto Build: Run选项,将其 ...
- prism App.config 配置
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSe ...
- PHP加速器eAccelerator安装
程序说明 eAccelerator是一个自由开放源码php加速器,优化和动态内容缓存,提高了php脚本的缓存性能,使得PHP脚本在编译的状态下,对 服务器的开销几乎为零. 它还有对脚本起优化作用,以加 ...
- java:Mybatis框架1(基本配置,log4j,Junit4(单元测试))
1.mybatis01: db.properties: driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/test userna ...
- java:Hibernate框架3(使用Myeclipse逆向工程生成实体和配置信息,hql语句各种查询(使用hibernate执行原生SQL语句,占位符和命名参数,封装Vo查询多个属性,聚合函数,链接查询,命名查询),Criteria)
1.使用Myeclipse逆向工程生成实体和配置信息: 步骤1:配置MyEclipse Database Explorer: 步骤2:为项目添加hibernate的依赖: 此处打开后,点击next进入 ...
- P2429 【制杖题】
这题目名字也是够了... emmmmmm为什么要用线筛??????不感觉很麻烦吗??????既然是智障制杖题,那么肯定要用很简单的算法啦~下面,我就提供一种非常便于理解的膜你算法~~~很明显,做了这题 ...
- 应用安全 - 免杀 - 工具 - the-backdoor-factory - 使用|命令 - 汇总
安装 Kali下方式一: git clone https://github.com/secretsquirrel/the-backdoor-factory方式二: apt-get install ba ...
- USACO1.6 Superprime Rib
题目传送门 每一个特殊质数都会被从右边切掉,所以除了首位外的其它位数一定都不会是偶数,只能是$1$,$3$,$5$,$7$,$9$ 而每一个特殊质数的首位一定是质数,也就是$2$,$3$,$5$,$7 ...
- 快速生成500W测试数据库
快速生成500W测试数据库: 创建测试表: DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(10) NOT NULL AUTO_ ...