0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

为什么可以用迭代法来实现二叉树的前后中序遍历呢

算法与数据结构 来源:代码随想录 作者:程序员Carl 2022-07-19 11:50 次阅读

二叉树的迭代遍历

看完本篇大家可以使用迭代法,再重新解决如下三道leetcode上的题目:

144.二叉树的前序遍历

94.二叉树的中序遍历

145.二叉树的后序遍历

为什么可以用迭代法(非递归的方式)来实现二叉树的前后中序遍历呢?

我们在栈与队列:匹配问题都是栈的强项中提到了,递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。

此时大家应该知道我们用栈也可以是实现二叉树的前后中序遍历了。

前序遍历(迭代法)

我们先看一下前序遍历。

前序遍历是中左右,每次先处理的是中间节点,那么先将跟节点放入栈中,然后将右孩子加入栈,再加入左孩子。

为什么要先加入 右孩子,再加入左孩子呢?因为这样出栈的时候才是中左右的顺序。

动画如下:

46663e9e-0712-11ed-ba43-dac502259ad0.gif

不难写出如下代码: (注意代码中空节点不入栈

classSolution{
public:
vectorpreorderTraversal(TreeNode*root){
stackst;
vectorresult;
if(root==NULL)returnresult;
st.push(root);
while(!st.empty()){
TreeNode*node=st.top();//中
st.pop();
result.push_back(node->val);
if(node->right)st.push(node->right);//右(空节点不入栈)
if(node->left)st.push(node->left);//左(空节点不入栈)
}
returnresult;
}
};

此时会发现貌似使用迭代法写出前序遍历并不难,确实不难。

此时是不是想改一点前序遍历代码顺序就把中序遍历搞出来了?

其实还真不行!

但接下来,再用迭代法写中序遍历的时候,会发现套路又不一样了,目前的前序遍历的逻辑无法直接应用到中序遍历上。

中序遍历(迭代法)

为了解释清楚,我说明一下 刚刚在迭代的过程中,其实我们有两个操作:

处理:将元素放进result数组中

访问:遍历节点

分析一下为什么刚刚写的前序遍历的代码,不能和中序遍历通用呢,因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。

那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。

那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

动画如下:

469ddb06-0712-11ed-ba43-dac502259ad0.gif

中序遍历,可以写出如下代码:

classSolution{
public:
vectorinorderTraversal(TreeNode*root){
vectorresult;
stackst;
TreeNode*cur=root;
while(cur!=NULL||!st.empty()){
if(cur!=NULL){//指针来访问节点,访问到最底层
st.push(cur);//将访问的节点放进栈
cur=cur->left;//左
}else{
cur=st.top();//从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
st.pop();
result.push_back(cur->val);//中
cur=cur->right;//右
}
}
returnresult;
}
};

后序遍历(迭代法)

再来看后序遍历,先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了,如下图:

46bcc85e-0712-11ed-ba43-dac502259ad0.png

所以后序遍历只需要前序遍历的代码稍作修改就可以了,代码如下:

classSolution{
public:
vectorpostorderTraversal(TreeNode*root){
stackst;
vectorresult;
if(root==NULL)returnresult;
st.push(root);
while(!st.empty()){
TreeNode*node=st.top();
st.pop();
result.push_back(node->val);
if(node->left)st.push(node->left);//相对于前序遍历,这更改一下入栈顺序(空节点不入栈)
if(node->right)st.push(node->right);//空节点不入栈
}
reverse(result.begin(),result.end());//将结果反转之后就是左右中的顺序了
returnresult;
}
};

总结

此时我们用迭代法写出了二叉树的前后中序遍历,大家可以看出前序和中序是完全两种代码风格,并不想递归写法那样代码稍做调整,就可以实现前后中序。

这是因为前序遍历中访问节点(遍历节点)和处理节点(将元素放进result数组中)可以同步处理,但是中序就无法做到同步!

上面这句话,可能一些同学不太理解,建议自己亲手用迭代法,先写出来前序,再试试能不能写出中序,就能理解了。

那么问题又来了,难道 二叉树前后中序遍历的迭代法实现,就不能风格统一么(即前序遍历 改变代码顺序就可以实现中序 和 后序)?

当然可以,这种写法,还不是很好理解,我们将在下一篇文章里重点讲解,敬请期待!

其他语言版本

Java

//前序遍历顺序:中-左-右,入栈顺序:中-右-左
classSolution{
publicListpreorderTraversal(TreeNoderoot){
Listresult=newArrayList<>();
if(root==null){
returnresult;
}
Stackstack=newStack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNodenode=stack.pop();
result.add(node.val);
if(node.right!=null){
stack.push(node.right);
}
if(node.left!=null){
stack.push(node.left);
}
}
returnresult;
}
}

//中序遍历顺序:左-中-右入栈顺序:左-右
classSolution{
publicListinorderTraversal(TreeNoderoot){
Listresult=newArrayList<>();
if(root==null){
returnresult;
}
Stackstack=newStack<>();
TreeNodecur=root;
while(cur!=null||!stack.isEmpty()){
if(cur!=null){
stack.push(cur);
cur=cur.left;
}else{
cur=stack.pop();
result.add(cur.val);
cur=cur.right;
}
}
returnresult;
}
}

//后序遍历顺序左-右-中入栈顺序:中-左-右出栈顺序:中-右-左,最后翻转结果
classSolution{
publicListpostorderTraversal(TreeNoderoot){
Listresult=newArrayList<>();
if(root==null){
returnresult;
}
Stackstack=newStack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNodenode=stack.pop();
result.add(node.val);
if(node.left!=null){
stack.push(node.left);
}
if(node.right!=null){
stack.push(node.right);
}
}
Collections.reverse(result);
returnresult;
}
}

Python

#前序遍历-迭代-LC144_二叉树的前序遍历
classSolution:
defpreorderTraversal(self,root:TreeNode)->List[int]:
#根结点为空则返回空列表
ifnotroot:
return[]
stack=[root]
result=[]
whilestack:
node=stack.pop()
#中结点先处理
result.append(node.val)
#右孩子先入栈
ifnode.right:
stack.append(node.right)
#左孩子后入栈
ifnode.left:
stack.append(node.left)
returnresult

#中序遍历-迭代-LC94_二叉树的中序遍历
classSolution:
definorderTraversal(self,root:TreeNode)->List[int]:
ifnotroot:
return[]
stack=[]#不能提前将root结点加入stack中
result=[]
cur=root
whilecurorstack:
#先迭代访问最底层的左子树结点
ifcur:
stack.append(cur)
cur=cur.left
#到达最左结点后处理栈顶结点
else:
cur=stack.pop()
result.append(cur.val)
#取栈顶元素右结点
cur=cur.right
returnresult

#后序遍历-迭代-LC145_二叉树的后序遍历
classSolution:
defpostorderTraversal(self,root:TreeNode)->List[int]:
ifnotroot:
return[]
stack=[root]
result=[]
whilestack:
node=stack.pop()
#中结点先处理
result.append(node.val)
#左孩子先入栈
ifnode.left:
stack.append(node.left)
#右孩子后入栈
ifnode.right:
stack.append(node.right)
#将最终的数组翻转
returnresult[::-1]


审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2904

    浏览量

    102991
  • 迭代法
    +关注

    关注

    0

    文章

    4

    浏览量

    6228
  • 二叉树
    +关注

    关注

    0

    文章

    74

    浏览量

    12238
  • python
    +关注

    关注

    51

    文章

    4675

    浏览量

    83466

原文标题:听说递归能做的,栈也能做!

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    matlab牛顿迭代法全解

    非线性方程(或方程组)问题可以描述为求 x 使得f(x) = 0。在求解非线性方程的方法,牛顿迭代法是求非线性方程(非线性方程组)数值解的一种重要的方法。牛顿是微积分创立者之一,微积分
    发表于 03-08 16:22

    计算机二叉树的问题

    各位大神,本人马上要考计算机级了,那个二叉树老是弄不明白,比如一个题目,一棵二叉树共有25个节点,其中五个叶子节点,则度为1的节点数为?
    发表于 09-04 09:45

    二叉树删除算法

    二叉树的删除操作主要是寻找替代点来进行替换操作。方法:先右转,再一直左转,直到左连接为空的那个点。然后,摘取出来,完成链接指向操作。public void deletmin(void){ root
    发表于 12-30 20:55

    二叉查找(GIF动图讲解)

    二叉查找(Binary Search Tree),也称二叉搜索,是指一棵空或者具有下列性质的二叉树
    发表于 07-29 15:24

    【案例分享】经典的压缩算法Huffman算法

    Priority为2+2=4的结点,然后再放回Priority Queue :继续我们的算法(我们可以看到,这是一种自底向上的建树的过程):最终我们会得到下面这样一棵二叉树:此时,我们把这个
    发表于 07-17 04:30

    C语言构建一个二叉树失败

    ; ,tree.leftnode->data);这三个输出都是乱码,我不知道二叉树的建立过程问题在哪里,我应该在每个节点都分配了内存,不过结果好像有问题}
    发表于 03-24 03:47

    什么是“红黑”看了就知道

    今天我们要说的红黑就是就是一棵非严格均衡的二叉树,均衡二叉树又是在二叉搜索的基础上增加了自动维持平衡的性质,插入、搜索、删除的效率都比较
    发表于 10-27 17:00

    Java编程求二叉树的镜像两种方法介绍

    给出一棵二叉树,求它的镜像,如下图:右边是二叉树是左边二叉树的镜像。仔细分析这两棵的特点,看看能不能总结出求镜像的步骤。这两棵的根节点相
    发表于 12-16 16:25

    MySQL数据库索引的底层是怎么实现

    左右子树也都是平衡二叉树。上图中举了1到10在二叉树的存储方式图的例子。每个节点只能有一个节点。B:上图中的B。B
    发表于 07-28 15:30

    二叉树层次遍历算法的验证

    实现二叉树的层次遍历算法,并对用”A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))”创建的二叉树进行测试。
    发表于 11-28 01:05 1908次阅读
    <b class='flag-5'>二叉树</b>层次<b class='flag-5'>遍历</b>算法的验证

    4中二叉树遍历方式介绍

    对于一种数据结构而言,遍历是常见操作。二叉树是一种基本的数据结构,是一种每个节点的儿子数目都不多于2的树。
    的头像 发表于 04-27 17:23 4539次阅读
    4中<b class='flag-5'>二叉树</b>的<b class='flag-5'>遍历</b>方式介绍

    二叉树的前序遍历非递归实现

    通过下面这个动画复习一下二叉树的前序遍历迭代遍历 我们试想一下,之前我们借助队列帮我们实现二叉树
    的头像 发表于 05-28 13:59 1705次阅读

    迭代法编写二叉树前后中序遍历案例

    递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
    发表于 07-25 15:40 375次阅读

    二叉树的统一迭代法

    我们以中序遍历为例,在二叉树:听说递归能做的,栈也能做!中提到说使用栈的话,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。
    发表于 08-03 11:22 350次阅读

    二叉树的代码实现

    二叉树的主要操作有遍历,例如有先序遍历、中序遍历、后序遍历。在遍历之前,就是创建一棵
    的头像 发表于 01-18 10:41 906次阅读
    <b class='flag-5'>二叉树</b>的代码<b class='flag-5'>实现</b>