树的前序遍历与中序遍历构造二叉树和树的中序遍历与后序遍历构造二叉树
Posted 允歆辰丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树的前序遍历与中序遍历构造二叉树和树的中序遍历与后序遍历构造二叉树相关的知识,希望对你有一定的参考价值。
目录
一.树的前序遍历与中序遍历构造二叉树
1.题目描述
给定两个整数数组
preorder
和inorder
,其中preorder
是二叉树的先序遍历,inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
力扣:力扣
2.问题分析
我们根据 preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]来分析如何手动构建一颗二叉树
① 首先根据前序遍历的特点,通过preorder我们可知3为根结点,再根据中序遍历的特点,通过inorder 我们可知3为根结点的左子树有9,右子树有15,20,7这些元素
② 因为已经划分了左右子树,再看preorder,此时9为结点3左子树的根结点,20为结点3右子树的根结点,再看inorder ,此时左子树已经完成,结点20的左子树有15,右子树有7;
③ 因为已经划分了左右子树,再看preorder,此时15为结点20左子树的根结点,20为结点3右子树的根结点,,此时preorder数组已经遍历完毕,整棵树也已经构建完毕了
手动已经实现了二叉树的构建,其实关键一共就两步,第一步是在前序遍历列表寻找根结点(也就是子树的第一个元素),第二步是在中序遍历寻找根结点的位置,根结点左边为左子树的结点范围,右边为右子树的结点范围.
接下来我们看代码实现(看代码实现的代码),代码递归实现和我们手动实现的顺序不一样,我们是同时寻找左右子树,递归是先构建根结点,然后构建左子树,最后构建右子树,直到全部遍历完成.(按照前序遍历(根左右)的方式)
大致的顺序如下:没有一步步递归,只画出了关键的步骤
其实不加 if(index==preorder.length)return null; 判断也没有错,只不过加上更加直观
3.代码实现
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
public TreeNode buildTree(int[] preorder, int[] inorder)
for (int i = 0; i < inorder.length; ++i)
map.put(inorder[i], i);
return buildTreeHelper(preorder,inorder,0,inorder.length-1);
int index=0;
public TreeNode buildTreeHelper(int[] preorder, int[] inorder,int left,int right)
if(index==preorder.length)
return null;
if(left>right)
return null;
//1.前序数组的第一个为根结点
int val=preorder[index++];
TreeNode root=new TreeNode(val);
//2.寻找中序遍历的左右子树的范围
int mid=map.get(val);
root.left=buildTreeHelper(preorder,inorder,left,mid-1);
root.right=buildTreeHelper(preorder,inorder,mid+1,right);
return root;
二.树的中序遍历与后序遍历构造二叉树
1.题目描述
给定两个整数数组
inorder
和postorder
,其中inorder
是二叉树的中序遍历,postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
力扣:力扣
2.问题分析
我们根据 inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]来分析如何手动构建一颗二叉树
树的中序遍历与后序遍历和前边那个构建二叉树还是有稍微的不同的,但是大体思路是一致的.
中序和后序需要根据后序遍历来选取根结点,根结点是子树结点范围中的最后一个,例如postorder = [9,15,7,20,3]的根结点是最后一个3;并且代码实现index从后向前遍历,先构建的右子树
① 首先根据后序遍历的特点,通过postorder我们可知3为根结点,再根据中序遍历的特点,通过inorder 我们可知3为根结点的左子树有9,右子树有15,20,7这些元素
② 因为已经划分了左右子树,再看postorder,此时9为结点3左子树的根结点,20为结点3右子树的根结点,再看inorder ,此时左子树已经完成,结点20的左子树有15,右子树有7;
③ 因为已经划分了左右子树,再看preorder,此时15为结点20左子树的根结点,20为结点3右子树的根结点,,此时preorder数组已经遍历完毕,整棵树也已经构建完毕了
由上面可以看出中序和后序构建二叉树和前序和中序构建二叉树的思路是一样的,实现也是一样的,代码递归构建却有很大的不同,代码递归实现和我们手动实现的顺序不一样,我们是同时寻找左右子树,递归是构建根结点,然后构建好右子树,最后构建左子树,直到全部遍历完成.(按照中右左的方式)
其实不加 if(index==preorder.length)return null; 判断也没有错,只不过加上更加直观
3.代码实现
Map<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] inorder, int[] postorder)
for (int i = 0; i < inorder.length; ++i)
map.put(inorder[i], i);
index = postorder.length - 1;
return findNode(inorder, postorder, 0, inorder.length - 1);
//这个index是postOrder的index
int index;
public TreeNode findNode(int[] inorder, int[] postorder, int left, int right)
if (index < 0)
return null;
if (left > right)
return null;
//1.从postorder找到根结点
int val = postorder[index--];
TreeNode root = new TreeNode(val);
//2.从inorder找到左右子树
Integer mid = map.get(val);
//3.递归左子树
root.right = findNode(inorder, postorder, mid+1, right);
//4.递归右子树
root.left = findNode(inorder, postorder, left, mid-1);
return root;
三.问题思考
现在我们思考这样一个问题:我们是否可以根据前序和后序遍历的顺序,来构建一颗二叉树呢?
答案是不行的,我们观察前面两个题目可以知道:我们最关键的在于寻找根结点,并找到它的左子树的结点范围和右子树结点范围,但是根据前序和后序遍历,第一次我们可以找到根结点,但是我们无法判断它的左子树是哪些,右子树是哪些,因此无法根据这两种遍历方式构建一颗二叉树
leetcode二叉树
目录
1. 二叉树的前序遍历
给你二叉树的根节点
root
,返回它节点值的 前序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,2,3]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
解题思路
1. 如果树为空,直接返回
2. 如果树非空:从根节点位置开始遍历,因为前序遍历规则:根节点、左子树、右子树
a. 沿着根节点一直往左走,将所经过路径中的节点依次入栈,并访问。
b. 取栈顶元素,该元素取到后,其左子树要么为空,要么已经遍历,可以直接遍历该节点,对于该节点,其左子树已经遍历,该节点也已经遍历,剩余其右子树没有遍历,将其左子树当成一棵新的树开始遍历,继续a步骤。
(前序遍历时每走到一个节点就往ret数组中添加该节点,即第一次访问该节点时添加)
public List<Integer> preorderTraversal(TreeNode root)
List<Integer> ret = new ArrayList<>();
Stack<TreeNode> s = new Stack<>();
TreeNode cur = root;
while(cur != null || !s.empty())
// 每次循环表示要开始访问一颗树了,先将一颗树的左路节点都入栈并访问节点
// 剩余左路节点的右子树还没访问
while(cur != null)
ret.add(cur.val);
s.push(cur);
cur = cur.left;
// 取栈中的节点依次访问节点的右子树
cur = s.pop();
cur = cur.right;
return ret;
2. 二叉树的中序遍历
OJ:二叉树的中序遍历
给定一个二叉树的根节点
root
,返回 它的 中序 遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
解题思路
(1)空树,直接返回
(2)如果树非空:从根节点位置开始遍历,但此时根节点不能遍历,因为中序遍历规则:左子树、根节点、右子树
a. 沿着根节点一直往左走,将所经过路径中的节点依次入栈,直至当前根节点为空
b. 取栈顶元素,该元素取到后,其左子树要么为空,要么已经遍历,可以直接遍历该节点,对于该节点,其左子树已经遍历,该节点也已经遍历,剩余其右子树没有遍历,将其左子树当成一棵新的树开始遍历,继续a步骤
(简单来说就是将节点先入栈,出栈时再往ret数组中添加元素。第二次访问时添加该节点)
public List<Integer> inorderTraversal(TreeNode root)
List<Integer> ret = new ArrayList<>();
// 空树直接返回
if(null == root)
return ret;
Stack<TreeNode> s = new Stack<>();
TreeNode cur = root;
while(cur != null || !s.empty())
// 沿这cur一直往左侧走,找到该条路径中最左侧的节点,并保存其所经路径中的所有节点
while(cur != null)
s.push(cur);
cur = cur.left;
// 获取根节点,直接遍历,因为其左侧是空树
cur = s.peek();
s.pop();
ret.add(cur.val);
// cur的左子树已经遍历,cur已经遍历,剩余其右子树没有遍历,
// 将其右子树当成一棵新的树进行遍历
cur = cur.right;
return ret;
3. 二叉树的后序遍历
OJ:二叉树的后序遍历
给你一棵二叉树的根节点
root
,返回其节点值的 后序遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[3,2,1]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
解题思路
(1)空树,直接返回
(2)如果树非空:从根节点位置开始遍历,但此时根节点不能遍历,因为后序遍历规则:左子树、右子树、根节点
a. 沿着根节点一直往左走,将所经过路径中的节点依次入栈
b. 取栈顶元素,该元素取到后,其左子树要么为空,要么已经遍历,但是此时该节点不能遍历,除非其右子树不存在或者其右子树已经遍历,才可以遍历该节点, 如果该节点右子树没有遍历,将其右子树作为一棵新的二叉树遍历,继续a步骤
(当第三次访问到节点时,将节点的值添加到lsit数组中)
public List<Integer> postorderTraversal(TreeNode root)
List<Integer> list = new ArrayList<>();
if(root == null)
return list;
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode cur = root;
TreeNode prev = null;
while(cur != null || !stack.isEmpty())
while(cur != null)
stack.push(cur);
cur = cur.left;
cur = stack.pop();
if(cur.right == null || cur.right == prev)
list.add(cur.val);
prev = cur;
cur = null;
else
stack.push(cur);
cur = cur.right;
return list;
4. 根据二叉树创建字符串
OJ:根据二叉树创建字符串
给你二叉树的根节点
root
,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。空节点使用一对空括号对
"()"
表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。
示例1:
输入:root = [1,2,3,4]
输出:"1(2(4))(3)"
解释:初步转化后得到 "1(2(4)())(3()())" ,但省略所有不必要的空括号对后,字符串应该是"1(2(4))(3)" 。
示例 2:
输入:root = [1,2,3,null,4]
输出:"1(2()(4))(3)"
解释:和第一个示例类似,但是无法省略第一个空括号对,否则会破坏输入与输出一一映射的关系。
解题思路
1. 如果树空,转化结束
2. 如果树非空
a. 先转化根节点
b. 转化根节点的左子树
如果根的左子树非空或者左子树空但是右子树非空:( 递归转化左子树 ), 注意将转化结果内嵌到()中
c. 转化根节点的右子树
如果根的右子树非空:( 递归转化右子树 ), 注意将转化结果内嵌到()中
class Solution
String str;
public String tree2str(TreeNode t)
StringBuilder sb = new StringBuilder();
tree2str(t, sb);
return sb.toString();
public void tree2str(TreeNode t, StringBuilder str)
if(null == t)
return;
// 先将根节点的数据放到str中
str.append(t.val);
// 处理根节点的左子树
if(null != t.left || null != t.right)
// 左子树非空,递归转化左子树
str.append("(");
tree2str(t.left, str);
str.append(")");
// 再检测t的右子树,如果右子树为空,不增加任何内容
if(null != t.right)
// 递归处理右子树
str.append("(");
tree2str(t.right, str);
str.append(")");
5. 从前序与中序遍历序列构造二叉树
给定两个整数数组
preorder
和inorder
,其中preorder
是二叉树的先序遍历,inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例 1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
示例 2:
输入: preorder = [-1], inorder = [-1]
输出: [-1]
解题思路
从前序遍历中获取根节点,中序遍历中根节点前面的是左子树的部分,中序遍历中根节点后面的是右子树部分。使用index下标来遍历前序数组。
1. 从前序遍历结果中获取到树的根节点
2. 在中序遍历结果中确定根节点的位置,按照该位置将中序遍历结果分为两部分
左半部分是根节点的左子树,递归创建根节点的左子树
右半部分是根节点的右子树,递归创建根节点的右子树
class Solution
public TreeNode buildTree(int[] preorder, int[] inorder)
return buildTreeHelper(preorder,inorder,0,inorder.length - 1);
int index = 0;
private TreeNode buildTreeHelper(int[] preorder, int[] inorder, int left, int right)
if(index == inorder.length )
return null;
if (left > right)
return null;
int pos = find(inorder,preorder);
TreeNode root = new TreeNode(preorder[index]);
index ++;
root.left = buildTreeHelper(preorder,inorder,left,pos - 1);
root.right = buildTreeHelper(preorder,inorder,pos + 1,right);
return root;
private int find(int[] inorder,int[] preorder)
for (int i = 0; i < inorder.length; i++)
if (inorder[i] == preorder[index])
return i;
return -1;
6. 从中序与后序遍历序列构造二叉树
给定两个整数数组
inorder
和postorder
,其中inorder
是二叉树的中序遍历,postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
示例 1:
输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]
示例 2:
输入:inorder = [-1], postorder = [-1]
输出:[-1]
解题思路
先看看同一颗树的前序遍历结果preorder = [3,9,20,15,7]和后序遍历的结果postorder = [9,15,7,20,3],对比一下这两。发现将后序遍历的结果反转->[3,20,7,15,9],前序遍历是根左右,后序遍历反转后的是根右左,所以我们只需要将后序遍历的结果反转一下,然后向上一题那样构建二叉树,只不过是先构建右子树再构建左子树。
class Solution
public TreeNode buildTree(int[] inorder, int[] postorder)
reverse(postorder);
return buildTreeHelper(postorder,inorder,0,inorder.length - 1);
private void reverse(int[] postorder)
for(int i = 0;i < postorder.length/2;i ++)
int temp = postorder[i];
postorder[i] = postorder[postorder.length - i - 1];
postorder[postorder.length - i - 1] = temp;
int index = 0;
private TreeNode buildTreeHelper(int[] preorder, int[] inorder, int left, int right)
if(index == preorder.length )
return null;
if (left > right)
return null;
int val = preorder[index];
index ++;
int pos = find(inorder,val);
TreeNode root = new TreeNode(val);
root.right = buildTreeHelper(preorder,inorder,pos + 1,right);
root.left = buildTreeHelper(preorder,inorder,left,pos - 1);
return root;
private int find(int[] inorder,int val)
for (int i = 0; i < inorder.length; i++)
if (inorder[i] == val)
return i;
return -1;
7. 二叉树的最近公共祖先
OJ:二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点5和节点1的最近公共祖先是节点3
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点5和节点1的最近公共祖先是节点3
解题思路
这题不太熟,暂时不写思路。
class Solution
TreeNode lca;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
find(root,p,q);
return lca;
private boolean find(TreeNode root, TreeNode p, TreeNode q)
if(root == null)
return false;
int left = find(root.left,p,q) ? 1 : 0;
int right = find(root.right,p,q) ? 1 : 0;
int mid = (root == p || root == q) ? 1 : 0;
if(left + right + mid == 2)
lca = root;
return left + right + mid > 0;
以上是关于树的前序遍历与中序遍历构造二叉树和树的中序遍历与后序遍历构造二叉树的主要内容,如果未能解决你的问题,请参考以下文章
二叉树进阶题------前序遍历和中序遍历构造二叉树;中序遍历和后序遍历构造二叉树