16 二叉树的后序遍历(Binary Tree Postorder Traversal)

2023-11-12 00:38

本文主要是介绍16 二叉树的后序遍历(Binary Tree Postorder Traversal),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 1 题目
    • 2 描述
    • 3 解决方案
      • 3.1 递归算法
        • 3.1.1 遍历法(Traverse)
          • 思路
          • 源码
        • 3.1.2 分治法(Devide And Conquer)
          • 思路
          • 源码
      • 3.2 非递归算法
        • 3.2.1 二叉树遍历的非递归通用解法
          • 思路
          • 源码
          • 图解
      • 3.3 时间复杂度
      • 3.4 空间复杂度

1 题目

  二叉树的后序遍历(Binary Tree Postorder Traversal)

lintcode:题号——68,难度——easy

2 描述

  给出一棵二叉树,返回其节点值的后序遍历。

名词:

遍历

按照一定的顺序对树中所有节点进行访问的过程叫做树的遍历。

后序遍历

在二叉树中,首先遍历左子树,然后遍历右子树,最后访问根结点,在遍历左右子树时,仍然采用这种规则,这样的遍历方式叫做二叉树的后序遍历。

序列化规则:

使用大括号包裹节点序列来表示二叉树,首个数据为根节点,后面接着是其左儿子和右儿子节点值,"#"表示不存在该子节点,之后以此类推。

样例1:

输入:二叉树 = {1,2,3}
输出:[2,3,1]
解释:上述{1,2,3}序列按照序列化规则表示的二叉树如下

     1/ \2   3

按照后序遍历规则,先左右子树再根,顺序为[2,3,1]

样例2:

输入:二叉树 = {1,#,2,3}
输出:[1,3,2]
解释:上述{1,#,2,3}序列按照序列化规则表示的二叉树如下

        1/   \#       2/3

按照后序遍历规则,先左右子树再根,顺序为[3,2,1]

3 解决方案

  后序遍历与前序遍历1、中序遍历2一样可以使用递归和非递归两类方式,递归和非递归各自的优缺点在前序遍历的介绍中已提过了。

3.1 递归算法

  递归写法比较简单,注意防止死循环,明确递归的三要素。

递归的三要素:

  1. 递归的定义(递归函数的返回值、参数要如何定义)
  2. 递归的拆解(递归如何向下拆分)
  3. 递归的出口(递归的结束条件)

  递归又有两种形式,遍历和分治。

3.1.1 遍历法(Traverse)
思路

  遍历的整个过程可以看成是为了完成某项大任务,于是领导亲自下基层处理各项小任务,所有工作都“亲历亲为”,参与了所有工作最终完成了大任务。

遍历法的主要递归函数一般不带返回值,会拥有全局参数或者贯穿整个递归过程的函数参数用来处理数据。

源码

  C++版本:

/**
* @param root: A Tree
* @return: Postorder in ArrayList which contains node values.
*/
vector<int> postorderTraversal(TreeNode * root) {// write your code herevector<int> result;traverse(root, result);return result;
}// 遍历法的递归函数,没有返回值,函数参数result贯穿整个递归过程用来记录遍历的结果
void traverse(TreeNode * root, vector<int> & result) // 递归三要素之定义
{if (root == nullptr){return; // 递归三要素之出口}// 前、中、后序三种遍历在此处对子树与根节点的处理顺序不同,后续遍历先处理左子树,再处理右子树,最后处理根节点traverse(root->left, result); // 递归三要素之拆解traverse(root->right, result);result.push_back(root->val);
}
3.1.2 分治法(Devide And Conquer)
思路

  分治法的整个过程可以看成是为了完成某项大人物,于是领导把各项工作分派给各个下属,各个下属又把工作继续细分层层向下分派给基层人员,每一级都只需要获取下级完成的任务结果即可,最终所有层级任务都完成了,大任务也对应完成了。

分治法的主要递归函数一般需要有返回值,用来向上一层提供结果。

源码

  C++版本:

/**
* @param root: A Tree
* @return: Postorder in ArrayList which contains node values.
*/
// 由于主函数的形式已经符合分治法需要的形式(具有合适的返回值),直接使用主函数做为递归函数
vector<int> postorderTraversal(TreeNode * root) {// write your code herevector<int> result;if (root == nullptr){return result; // 递归三要素之出口}// 获取左子树的遍历结果vector<int> leftResult = postorderTraversal(root->left); // 递归三要素之拆解// 获取右子树的遍历结果vector<int> rightResult = postorderTraversal(root->right);// 前、中、后序三种遍历在此处对子树与根节点的处理顺序不同,后续遍历先处理左子树,再处理右子树,最后处理根节点result.insert(result.end(), leftResult.begin(), leftResult.end());result.insert(result.end(), rightResult.begin(), rightResult.end());result.push_back(root->val);return result;
}

3.2 非递归算法

3.2.1 二叉树遍历的非递归通用解法
思路

  在后序遍历的非递归算法中,采用针对二叉树的前、中、后序遍历都比较通用的形式来解决遍历问题,在该通用解法中,后序遍历与前序遍历1、中序遍历2的不同之处在于对节点的解析操作发生的时刻与处理方式,前序遍历是“边入栈边解析”,中序遍历是“出栈时解析”,后序遍历则是“出栈时判断条件解析”。

  1. 左穿入栈到底;
  2. 从栈内弹出当前节点,可能是左子树或者根节点(相对),出栈时判断解析;(前序遍历是在入栈时解析,中序是弹出时解析,后序是弹出时判断条件解析)

判断条件为:下一步是否需要解析右子树?
需要解析右子树:右子树非空且未被解析;(cur->right != nullptr && prev != cur->right)
不需要解析右子树:右子树为空或已经被解析过。

  1. 右子树的根节点入栈,重复1、2步骤(即对右子树做中序遍历),直到满足循环结束条件,该二叉树的后序遍历即在结果序列中。

后序遍历的初始条件和循环结束条件在通用解法中与前、中序遍历一致。
初始条件:栈为空当前指针指向根节点
循环结束条件:栈为空当前指针为空
后序遍历为了判断右子树是否已经被解析过,需要额外使用一个指针prev来记录上一个解析过的节点。

源码
/**
* @param root: A Tree
* @return: Postorder in ArrayList which contains node values.
*/
vector<int> postorderTraversal(TreeNode * root) {// write your code herevector<int> result;if (root == nullptr){return result;}stack<TreeNode *> nodeStack;TreeNode * prev = nullptr; // prev用来记录上一个解析过的节点TreeNode * cur = root;while (cur != nullptr || !nodeStack.empty()){while (cur != nullptr){nodeStack.push(cur);cur = cur->left;}cur = nodeStack.top();// 此处是后序遍历不同的地方,出栈时判断条件解析if (cur->right != nullptr && prev != cur->right) // 右子树存在且未被解析{cur = cur->right; // 右节点成为下一个要处理的节点}else // 右子树为空或者右子树已经被解析过了{result.push_back(cur->val);nodeStack.pop();// 后序遍历在此处比前、中序遍历多了两行prev = cur; // 使用prev记录解析过的节点cur = nullptr; // 将cur至空,防止重复解析存在右子树的节点}}return result;
}
图解

  在逻辑上,前、中、后序遍历的过程大致相同,可以参考前序遍历1的图解,区别只是解析操作发生的时刻和处理方式不同。

3.3 时间复杂度

  对二叉树的遍历,不管使用什么方式都是将整棵树中的所有节点走一遍,所以算法的时间复杂度都为O(n)。

  关于二叉树和分治法的时间复杂度的更多内容,在前序遍历1中已经进行了计算分析。

3.4 空间复杂度

  算法的空间复杂度,分治法为O(n),上述其余方法都为O(1)。


  1. 二叉树的前序遍历:https://blog.csdn.net/SeeDoubleU/article/details/119834420 ↩︎ ↩︎ ↩︎ ↩︎

  2. 二叉树的中序遍历:https://blog.csdn.net/SeeDoubleU/article/details/119943283 ↩︎ ↩︎

这篇关于16 二叉树的后序遍历(Binary Tree Postorder Traversal)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/393899

相关文章

基于Python实现一个Windows Tree命令工具

《基于Python实现一个WindowsTree命令工具》今天想要在Windows平台的CMD命令终端窗口中使用像Linux下的tree命令,打印一下目录结构层级树,然而还真有tree命令,但是发现... 目录引言实现代码使用说明可用选项示例用法功能特点添加到环境变量方法一:创建批处理文件并添加到PATH1

MySQL存储过程之循环遍历查询的结果集详解

《MySQL存储过程之循环遍历查询的结果集详解》:本文主要介绍MySQL存储过程之循环遍历查询的结果集,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言1. 表结构2. 存储过程3. 关于存储过程的SQL补充总结前言近来碰到这样一个问题:在生产上导入的数据发现

python进行while遍历的常见错误解析

《python进行while遍历的常见错误解析》在Python中选择合适的遍历方式需要综合考虑可读性、性能和具体需求,本文就来和大家讲解一下python中while遍历常见错误以及所有遍历方法的优缺点... 目录一、超出数组范围问题分析错误复现解决方法关键区别二、continue使用问题分析正确写法关键点三

Java遍历HashMap的6种常见方式

《Java遍历HashMap的6种常见方式》这篇文章主要给大家介绍了关于Java遍历HashMap的6种常见方式,方法包括使用keySet()、entrySet()、forEach()、迭代器以及分别... 目录1,使用 keySet() 遍历键,再通过键获取值2,使用 entrySet() 遍历键值对3,

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

uva 575 Skew Binary(位运算)

求第一个以(2^(k+1)-1)为进制的数。 数据不大,可以直接搞。 代码: #include <stdio.h>#include <string.h>const int maxn = 100 + 5;int main(){char num[maxn];while (scanf("%s", num) == 1){if (num[0] == '0')break;int len =

leetcode105 从前序与中序遍历序列构造二叉树

根据一棵树的前序遍历与中序遍历构造二叉树。 注意: 你可以假设树中没有重复的元素。 例如,给出 前序遍历 preorder = [3,9,20,15,7]中序遍历 inorder = [9,3,15,20,7] 返回如下的二叉树: 3/ \9 20/ \15 7   class Solution {public TreeNode buildTree(int[] pr

【JavaScript】LeetCode:16-20

文章目录 16 无重复字符的最长字串17 找到字符串中所有字母异位词18 和为K的子数组19 滑动窗口最大值20 最小覆盖字串 16 无重复字符的最长字串 滑动窗口 + 哈希表这里用哈希集合Set()实现。左指针i,右指针j,从头遍历数组,若j指针指向的元素不在set中,则加入该元素,否则更新结果res,删除集合中i指针指向的元素,进入下一轮循环。 /*** @param

树(Tree)——《啊哈!算法》

树 什么是树?树是一种特殊的图,不包含回路的连通无向树。 正因为树有着“不包含回路”这个特点,所以树就被赋予了很多特性。 一棵树中的任意两个结点有且仅有唯一的一条路径连通。一棵树如果有n个结点,那么它一定恰好有n-1条边。在一棵树中加一条边将会构成一个回路。 一棵树有且只有一个根结点。 没有父结点的结点称为根结点(祖先)。没有子结点的结点称为叶结点。如果一个结点既不是根结点也不是叶

PHP实现二叉树遍历(非递归方式,栈模拟实现)

二叉树定义是这样的:一棵非空的二叉树由根结点及左、右子树这三个基本部分组成,根据节点的访问位置不同有三种遍历方式: ① NLR:前序遍历(PreorderTraversal亦称(先序遍历)) ——访问结点的操作发生在遍历其左右子树之前。 ② LNR:中序遍历(InorderTraversal) ——访问结点的操作发生在遍历其左右子树之中(间)。 ③ LRN:后序遍历(PostorderT