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

    文章

    2957

    浏览量

    104535
  • 迭代法
    +关注

    关注

    0

    文章

    4

    浏览量

    6256
  • 二叉树
    +关注

    关注

    0

    文章

    74

    浏览量

    12311
  • python
    +关注

    关注

    55

    文章

    4781

    浏览量

    84440

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

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

收藏 人收藏

    评论

    相关推荐

    基于二叉树的时序电路测试序列设计

    为了实现时序电路状态验证和故障检测,需要事先设计一个输入测试序列。基于二叉树节点和树枝的特性,建立时序电路状态二叉树,按照电路二叉树节点(状态)与树枝(输入)的层次逻辑
    发表于 07-12 13:57 0次下载
    基于<b class='flag-5'>二叉树</b>的时序电路测试序列设计

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

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

    4中二叉树遍历方式介绍

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

    详解电源二叉树到底是什么

    作为数据结构的基础,分很多种,像 AVL 、红黑二叉搜索....今天我想分享的是关于二叉树
    的头像 发表于 06-06 15:05 9955次阅读
    详解电源<b class='flag-5'>二叉树</b>到底是什么

    面试算法之重建二叉树

    节点?左子节点有几个?很显然我们是不知道的,由此可以得出,只知道前序遍历是不可能反推出二叉树的,
    的头像 发表于 11-27 15:59 2532次阅读

    面试二叉树看这11个就够了

    根据前、遍历的特点,(根左右、左根右),先根据前序遍历确定根节点,然后在
    的头像 发表于 11-27 16:25 3309次阅读

    二叉树操作的相关知识和代码详解

    见的二叉树操作作个总结: 前序遍历遍历,后序遍历; 层次
    的头像 发表于 12-12 11:04 2018次阅读
    <b class='flag-5'>二叉树</b>操作的相关知识和代码详解

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

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

    C语言编程如何求出二叉树后序遍历

    题目 已知二叉树前序为 ABDFGCEH 后序序列为 BFDGACEH ,要求输出后序遍历为 FGDBHECA 大体思路 又先得出根,先的根后为左
    的头像 发表于 08-23 11:04 3872次阅读

    C语言数据结构:什么是二叉树

    完全二叉树:完全二叉树是效率很高的数据结构。对于深度为K,有n个节点的二叉树,当且仅当每一个节点都与深度为K的满二叉树编号从1至n的节点一
    的头像 发表于 04-21 16:20 2472次阅读

    怎么就能构造成二叉树

    一直跟着公众号学算法的录友 应该知道,我在二叉树:构造二叉树登场!,已经讲过,只有 与后序 和
    的头像 发表于 07-14 11:20 1515次阅读

    二叉树的统一迭代法

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

    使用C语言代码实现平衡二叉树

    这篇博客主要总结平衡二叉树,所以,二叉排序树知识不会提及,但是会用到。
    的头像 发表于 09-21 11:00 1049次阅读

    二叉树的代码实现

    二叉树的主要操作有遍历,例如有先遍历遍历、后
    的头像 发表于 01-18 10:41 1200次阅读
    <b class='flag-5'>二叉树</b>的代码<b class='flag-5'>实现</b>

    解析LeetCode第226号题目:反转二叉树

    *简单讲就是把每个节点的左子树和右子树进行交换** 。 显然,这需要我们能够遍历二叉树。 那么遍历二叉树就有两种经典的解法:深度优先
    的头像 发表于 02-17 14:52 829次阅读
    解析LeetCode第226号题目:反转<b class='flag-5'>二叉树</b>