问题描述
- 给定一个二叉树的根节点 root ,返回 它的 中序 遍历
示例1:
输入:root = [1,null,2,3]
输出:[1,3,2]
示例2:
输入:root = []
输出:[]
示例3:
输入:root = [1]
输出:[1]
题目来源:LeetCode官方
链接地址:https://leetcode.cn/problems/binary-tree-inorder-traversal/
思路
这里我们主要提供三种解法:
1.递归
2.用栈辅助
3.标记法
题目虽然是求中序遍历,但我们可以把它扩展到先序与后序遍历,具体做法见代码
核心代码
递归:
/**
* 这个前,中,后是针对根节点而言的,递归实现
*
* @param root
* @return
*/
public static List<Integer> orderTraversal_dg(TreeNode root) {
List<Integer> list = new ArrayList<>();
// preorder(root, list);
// inorder(root, list);
postorder(root, list);
return list;
}
//前序遍历,PLR
//中序遍历,LPR
//后序遍历,LRP
public static void preorder(TreeNode root, List<Integer> list) {//这里引入list,用于保存遍历的顺序
if (root == null) return;
list.add(root.val);
preorder(root.left, list);//递归遍历左子树
preorder(root.right, list);//递归遍历右子树
}
/**
* 递归的核心部分
* @param root
* @param list
*/
public static void inorder(TreeNode root, List<Integer> list) {//这里引入list,用于保存遍历的顺序
if (root == null) return;
//在递归的作用下,程序会一直遍历到二叉树的最底层左叶子结点,然后将其值记录,然后回到上一层递归,记录根结点,再访问右结点
inorder(root.left, list);//递归遍历左子树
list.add(root.val);//前序和后续只需要将该语句放到相应位置即可
inorder(root.right, list);//递归遍历右子树
}
/**
* 递归的核心部分
* @param root
* @param list
*/
public static void postorder(TreeNode root, List<Integer> list) {//这里引入list,用于保存遍历的顺序
if (root == null) return;
postorder(root.left, list);//递归遍历左子树
postorder(root.right, list);//递归遍历右子树
list.add(root.val);
}
代码解释
递归的核心是preorder、inorder和postorder三个方法,orderTraversal_dg通过调用这三个方法完成操作。list.add(root.val);语句的位置,决定了序列的输出
栈迭代:
//前序遍历
public static List<Integer> preTraversal_stack(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
//当栈不空或者root非空的情况下,我们循环,因为这说明还有元素要处理
while(root != null || !stack.isEmpty()){
//只要结点不空,我们就将其及其左结点入栈
while (root != null){
list.add(root.val);//将值加入到list中
stack.push(root);
root = root.left;
}
//入栈结束后,就可以将值加入到list中了
root = stack.pop();//更新root
root = root.right;//尝试看root有没有右结点,没有就将root置空,进入下一次循环
}
return list;
}
/**
* 这里我们用栈来解决
* @param root
* @return
*/
public static List<Integer> inorderTraversal_stack(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
//当栈不空或者root非空的情况下,我们循环,因为这说明还有元素要处理
while(root != null || !stack.isEmpty()){
//只要结点不空,我们就将其及其左结点入栈
while (root != null){
stack.push(root);
root = root.left;
}
//入栈结束后,就可以将值加入到list中了
root = stack.pop();//更新root
list.add(root.val);//将值加入到list中
root = root.right;//尝试看root有没有右结点,没有就将root置空,进入下一次循环
}
return list;
}
public static List<Integer> postTraversal_stack(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.push(root);//先将根结点入栈
//当栈不空或者root非空的情况下,我们循环,因为这说明还有元素要处理
while(!stack.isEmpty()){
root = stack.pop();//出栈
list.add(root.val);//将值加入到list中
//只要结点不空,我们就将其及其左结点入栈
if (root.left != null) stack.push(root.left);
if (root.right != null) stack.push(root.right);
}
//因为以上述代码得到的list与我们想要的后序遍历输出顺序相反,所以这里进行逆序操作
Collections.reverse(list);//使用Collections类里头的方法将list反转
return list;
}
代码解释
栈迭代的时间和空间优化是最好的,如果只考虑前序后中序,选栈迭代最佳,因为你只要记住了中序,前序只要调换位置就行,但如果是后序遍历,那么栈迭代就不太友好了,你会发现后序的代码与前序、中序差异较大,并不想递归那样好记
标记法:
//因为题目给的TreeNode结构体不包含flag,所以我们将flag和原本的TreeNode封装成Node
class Node{
Boolean flag = false;//表示结点的访问状态,默认false,表示未访问
TreeNode treenode;
Node(TreeNode treenode){
this.treenode = treenode;
}
Node(Boolean flag, TreeNode node) {
this.flag = flag;
this.treenode = node;
}
}
// 使用flag标记节点的状态,新节点为false,已访问的节点为true。
// 如果遇到的节点为false,则将其标记为true,然后将其右子节点、自身、左子节点依次入栈。
// 如果遇到的节点为true,则将节点的值输出。
//----此法比递归效率高,相比于栈迭代逊色一些,但好处在于,只要掌握一种,就能像递归一样随意得到另外两种,只需要交换核心代码顺序即可
//----栈迭代法如果只是前序和中序,那还好,如果是后序,那代码改动范围太大,不太友好
//前序遍历
public static List<Integer> preTraversal(TreeNode root) {
Stack<Node> stack = new Stack<>();//定义工具栈
List<Integer> list = new ArrayList<>();
Node roots = new Node(root);
stack.push(roots);//将初始结点入栈
//栈不空,加入循环
while(!stack.isEmpty()){
Node node = stack.pop();
if (node.treenode == null)continue;//如果结点是空的,那我们就跳过此次循环
if (!node.flag){//该结点没有访问过,按照右子结点,自己,左子结点顺序入栈,并修改自己的flag,表示已访问
//这里是因为stack为Node类型,所以我们要把左右结点封装成Node类型,便于入栈
Node left = new Node(node.treenode.left);
Node right = new Node(node.treenode.right);
//改变前,中,后序遍历的关键点,因为引入了栈,所以right、left、node的入栈顺序的反序就是遍历的结果
//比如说这里是right,left,node入栈顺序,那么输出结果就是node,left,right,将node看成根结点
stack.push(right);
stack.push(left);
node.flag = true;//修改访问轨迹
stack.push(node);
}else{
list.add(node.treenode.val);
}
}
return list;
}
/**
* 标记法,false未访问,true已访问
* @param root
* @return
*/
public static List<Integer> inorderTraversal(TreeNode root) {
Stack<Node> stack = new Stack<>();//定义工具栈
List<Integer> list = new ArrayList<>();
Node roots = new Node(root);
stack.push(roots);//将初始结点入栈
//栈不空,加入循环
while(!stack.isEmpty()){
Node node = stack.pop();
if (node.treenode == null)continue;//如果结点是空的,那我们就跳过此次循环
if (!node.flag){//该结点没有访问过,按照右子结点,自己,左子结点顺序入栈,并修改自己的flag,表示已访问
//这里是因为stack为Node类型,所以我们要把左右结点封装成Node类型,便于入栈
Node left = new Node(node.treenode.left);
Node right = new Node(node.treenode.right);
//改变前,中,后序遍历的关键点,因为引入了栈,所以right、left、node的入栈顺序的反序就是遍历的结果
//比如说这里是right,node,left入栈顺序,那么输出结果就是left,node,right,将node看成根结点
stack.push(right);
node.flag = true;//修改访问轨迹
stack.push(node);
stack.push(left);
}else{
list.add(node.treenode.val);
}
}
return list;
}
//后序遍历
public static List<Integer> postTraversal(TreeNode root) {
Stack<Node> stack = new Stack<>();//定义工具栈
List<Integer> list = new ArrayList<>();
Node roots = new Node(root);
stack.push(roots);//将初始结点入栈
//栈不空,加入循环
while(!stack.isEmpty()){
Node node = stack.pop();
if (node.treenode == null)continue;//如果结点是空的,那我们就跳过此次循环
if (!node.flag){//该结点没有访问过,按照右子结点,自己,左子结点顺序入栈,并修改自己的flag,表示已访问
//这里是因为stack为Node类型,所以我们要把左右结点封装成Node类型,便于入栈
Node left = new Node(node.treenode.left);
Node right = new Node(node.treenode.right);
//改变前,中,后序遍历的关键点,因为引入了栈,所以right、left、node的入栈顺序的反序就是遍历的结果
//比如说这里是node,right,left入栈顺序,那么输出结果就是left,right,node,将node看成根结点
node.flag = true;//修改访问轨迹
stack.push(node);
stack.push(right);
stack.push(left);
}else{
list.add(node.treenode.val);
}
}
return list;
}
代码解释
无论是哪一种遍历,遍历结果左结点一定在右结点的前面,所以在栈中,该层右结点一定要比左结点先入栈;也就是说stack.push(right);要在stack.push(left);前面
运行效果
栈迭代中序
标记法中序
完整源代码
package com.easy;
import java.util.*;
//Definition for a binary tree node.
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
//因为题目给的TreeNode结构体不包含flag,所以我们给加一个
class Node{
Boolean flag = false;//表示结点的访问状态,默认false
TreeNode treenode;
Node(TreeNode treenode){
this.treenode = treenode;
}
Node(Boolean flag, TreeNode node) {
this.flag = flag;
this.treenode = node;
}
}
public class inorderTraversal {
public static void main(String[] args) {
/**
* 构建二叉树
* 1
* 2 3
* 4 5 6
*/
TreeNode n4 = new TreeNode(4);
TreeNode n5 = new TreeNode(5);
TreeNode n6 = new TreeNode(6);
TreeNode n2 = new TreeNode(2, n4, n5);
TreeNode n3 = new TreeNode(3, n6, null);
TreeNode root = new TreeNode(1, n2, n3);
//递归
List<Integer> list1 = orderTraversal_dg(root);
//栈迭代
// List<Integer> list = inorderTraversal_stack(root);
// List<Integer> list = preTraversal_stack(root);
List<Integer> list2 = postTraversal_stack(root);
//标记法
// List<Integer> list = preTraversal(root);
// List<Integer> list = inorderTraversal(root);
List<Integer> list3 = postTraversal(root);
//后序遍历结果
list1.forEach(item-> System.out.print(item + " "));
System.out.println();
list2.forEach(item-> System.out.print(item + " "));
System.out.println();
list3.forEach(item-> System.out.print(item + " "));
}
//-------------------------------------------------------------------------------
// 使用flag标记节点的状态,新节点为false,已访问的节点为true。
// 如果遇到的节点为false,则将其标记为true,然后将其右子节点、自身、左子节点依次入栈。
// 如果遇到的节点为true,则将节点的值输出。
//----此法比递归效率高,相比于栈迭代逊色一些,但好处在于,只要掌握一种,就能像递归一样随意得到另外两种,只需要交换核心代码顺序即可
//----栈迭代法如果只是前序和中序,那还好,如果是后序,那代码改动范围太大,不太友好
//前序遍历
public static List<Integer> preTraversal(TreeNode root) {
Stack<Node> stack = new Stack<>();//定义工具栈
List<Integer> list = new ArrayList<>();
Node roots = new Node(root);
stack.push(roots);//将初始结点入栈
//栈不空,加入循环
while(!stack.isEmpty()){
Node node = stack.pop();
if (node.treenode == null)continue;//如果结点是空的,那我们就跳过此次循环
if (!node.flag){//该结点没有访问过,按照右子结点,自己,左子结点顺序入栈,并修改自己的flag,表示已访问
//这里是因为stack为Node类型,所以我们要把左右结点封装成Node类型,便于入栈
Node left = new Node(node.treenode.left);
Node right = new Node(node.treenode.right);
//改变前,中,后序遍历的关键点,因为引入了栈,所以right、left、node的入栈顺序的反序就是遍历的结果
//比如说这里是right,left,node入栈顺序,那么输出结果就是node,left,right,将node看成根结点
stack.push(right);
stack.push(left);
node.flag = true;//修改访问轨迹
stack.push(node);
}else{
list.add(node.treenode.val);
}
}
return list;
}
/**
* 标记法,false未访问,true已访问
* @param root
* @return
*/
public static List<Integer> inorderTraversal(TreeNode root) {
Stack<Node> stack = new Stack<>();//定义工具栈
List<Integer> list = new ArrayList<>();
Node roots = new Node(root);
stack.push(roots);//将初始结点入栈
//栈不空,加入循环
while(!stack.isEmpty()){
Node node = stack.pop();
if (node.treenode == null)continue;//如果结点是空的,那我们就跳过此次循环
if (!node.flag){//该结点没有访问过,按照右子结点,自己,左子结点顺序入栈,并修改自己的flag,表示已访问
//这里是因为stack为Node类型,所以我们要把左右结点封装成Node类型,便于入栈
Node left = new Node(node.treenode.left);
Node right = new Node(node.treenode.right);
//改变前,中,后序遍历的关键点,因为引入了栈,所以right、left、node的入栈顺序的反序就是遍历的结果
//比如说这里是right,node,left入栈顺序,那么输出结果就是left,node,right,将node看成根结点
stack.push(right);
node.flag = true;//修改访问轨迹
stack.push(node);
stack.push(left);
}else{
list.add(node.treenode.val);
}
}
return list;
}
//后序遍历
public static List<Integer> postTraversal(TreeNode root) {
Stack<Node> stack = new Stack<>();//定义工具栈
List<Integer> list = new ArrayList<>();
Node roots = new Node(root);
stack.push(roots);//将初始结点入栈
//栈不空,加入循环
while(!stack.isEmpty()){
Node node = stack.pop();
if (node.treenode == null)continue;//如果结点是空的,那我们就跳过此次循环
if (!node.flag){//该结点没有访问过,按照右子结点,自己,左子结点顺序入栈,并修改自己的flag,表示已访问
//这里是因为stack为Node类型,所以我们要把左右结点封装成Node类型,便于入栈
Node left = new Node(node.treenode.left);
Node right = new Node(node.treenode.right);
//改变前,中,后序遍历的关键点,因为引入了栈,所以right、left、node的入栈顺序的反序就是遍历的结果
//比如说这里是node,right,left入栈顺序,那么输出结果就是left,right,node,将node看成根结点
node.flag = true;//修改访问轨迹
stack.push(node);
stack.push(right);
stack.push(left);
}else{
list.add(node.treenode.val);
}
}
return list;
}
//-------------------------------------------------------------------------------
//前序遍历
public static List<Integer> preTraversal_stack(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
//当栈不空或者root非空的情况下,我们循环,因为这说明还有元素要处理
while(root != null || !stack.isEmpty()){
//只要结点不空,我们就将其及其左结点入栈
while (root != null){
list.add(root.val);//将值加入到list中
stack.push(root);
root = root.left;
}
//入栈结束后,就可以将值加入到list中了
root = stack.pop();//更新root
root = root.right;//尝试看root有没有右结点,没有就将root置空,进入下一次循环
}
return list;
}
/**
* 这里我们用栈来解决
* @param root
* @return
*/
public static List<Integer> inorderTraversal_stack(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
//当栈不空或者root非空的情况下,我们循环,因为这说明还有元素要处理
while(root != null || !stack.isEmpty()){
//只要结点不空,我们就将其及其左结点入栈
while (root != null){
stack.push(root);
root = root.left;
}
//入栈结束后,就可以将值加入到list中了
root = stack.pop();//更新root
list.add(root.val);//将值加入到list中
root = root.right;//尝试看root有没有右结点,没有就将root置空,进入下一次循环
}
return list;
}
public static List<Integer> postTraversal_stack(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.push(root);//先将根结点入栈
//当栈不空或者root非空的情况下,我们循环,因为这说明还有元素要处理
while(!stack.isEmpty()){
root = stack.pop();//出栈
list.add(root.val);//将值加入到list中
//只要结点不空,我们就将其及其左结点入栈
if (root.left != null) stack.push(root.left);
if (root.right != null) stack.push(root.right);
}
//因为以上述代码得到的list与我们想要的后序遍历输出顺序相反,所以这里进行逆序操作
Collections.reverse(list);//使用Collections类里头的方法将list反转
return list;
}
//-------------------------------------------------------------------------------
/**
* 这个前,中,后是针对根节点而言的,递归实现
*
* @param root
* @return
*/
public static List<Integer> orderTraversal_dg(TreeNode root) {
List<Integer> list = new ArrayList<>();
// preorder(root, list);
// inorder(root, list);
postorder(root, list);
return list;
}
//前序遍历,PLR
//中序遍历,LPR
//后序遍历,LRP
public static void preorder(TreeNode root, List<Integer> list) {//这里引入list,用于保存遍历的顺序
if (root == null) return;
list.add(root.val);
preorder(root.left, list);//递归遍历左子树
preorder(root.right, list);//递归遍历右子树
}
/**
* 递归的核心部分
* @param root
* @param list
*/
public static void inorder(TreeNode root, List<Integer> list) {//这里引入list,用于保存遍历的顺序
if (root == null) return;
//在递归的作用下,程序会一直遍历到二叉树的最底层左叶子结点,然后将其值记录,然后回到上一层递归,记录根结点,再访问右结点
inorder(root.left, list);//递归遍历左子树
list.add(root.val);//前序和后续只需要将该语句放到相应位置即可
inorder(root.right, list);//递归遍历右子树
}
public static void postorder(TreeNode root, List<Integer> list) {//这里引入list,用于保存遍历的顺序
if (root == null) return;
postorder(root.left, list);//递归遍历左子树
postorder(root.right, list);//递归遍历右子树
list.add(root.val);
}
//-------------------------------------------------------------------------------
}