注意可以从
前序与中序遍历中重构二叉树或中序遍历与后序遍历中重构二叉树
首先,我们要明白二叉树的遍历方式为前 中 后 与层序遍历
,其中前三种为dfs
。
- 前序遍历为
根节点 ----->左子树----->右子树的顺序
- 中序遍历为
左子树----->根节点----->右子树的顺序
- 后序遍历为
左子树----->右子树------>根的顺序
- 只要给出的遍历中有中序遍历,我们就可以推出在根节点的左侧为左子树,在根节点的右面为右子树
- 我们要明确的是如何确定根接点,在前序遍历中
序列的第一个值为根节点
,在后序遍历中序列的最后一个值为根节点
,通过在前后序遍历中确定的根节点的值再在中序遍历中确定根的位置,则在找到根的前面为左子树的值,在根的后面为右子树的值 - 根据左子树的节点的数量,
middle-left
,则可在`后序与前序遍历中确定左子树与右子树在序列中的位置(注意是根据元素的个数确定) - 从而可以转换为分治算法与递归接着处理
package JDFS;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/4/29 0029 17:12
* 从前序与中序序列中构造二叉树
* https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solution/si-lu-qing-xi-dai-ma-jian-ji-he-105ti-si-lu-yi-z-2/
*/
public class Problem105 {
//递归:
// 在中序遍历数组中位于其左边的一定是在坐下较
//根据画图可以看出
//固定住前序遍里数组中的元素,在中序遍历数组中进行查找
//则位于 中序遍历数组左侧的一定是其 root节点的左子树,位于其右侧的一定是柚子树
/**
* preOrder第一个元素为root,在inorder里面找到root,
* 在它之前的左子树(长为l1),之后右子树(l2),preOrder[1]到perOrder[l1]为左子树
* 之后为右子树分别递归哦
*
*/
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length==0&&inorder.length==0) return null;
return dfs(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
}
/**
*
* @param preOrder
* @param inorder
* @param inOrderleft
* @param inOrderright
* @param preOrderleft
* @param preOrderright
* @return
* 如今我们已经遇到了好几个类似的从数组重建二叉树的题目了,先来看几个关键词:递归、区间分治
*
* 很早之前我们说过,这种区间分治的题目,使用左闭右闭区间更加方便,如果坚持使用左闭右开区间,容易导致人懵逼。
* 在这个题目中也是类似的,我们使用左闭右闭区间。
重建二叉树的基本思路就是先构造根节点,再构造**左子树**,接下来构造**右子树**,其中,
构造**左子树**和**右子树**是一个子问题,递归处理即可。因此我们只关心如何构造根节点,以及如何递归构造左子树和右子树。
*递归函数的设计上,仍旧采用**左闭右闭**对数组局部进行描述。即一个数组,使用 3 个变量描述:
数组本身 arr
数组的起始位置 lo
数组的结束位置 hi
public TreeNode dfs(int[] preOrder,int[] inorder,int inOrderleft,int inOrderright,int preOrderleft,int preOrderright){
它的主要参数相比原来题目中的原型多了数组范围描述变量,这是我们所期望的,因为我们要做区间分治嘛。
*后面的故事就很简单了,三元组 (preorder, lo1, hi1) 描述的前序遍历数组,以及三元组 (inorder, lo2, hi2) 描述的中序遍历数组,如何从它们重建二叉树?递归的说法就是:
你会发现,递归创建左子树,
无非就是再构造一个新的前序遍历的三元组 (preorder, lo1+1, lo1+mid-lo2)
以及 (inorder, lo2, mid-1),其中 mid 是当前 inorder 中 root 的位置。
看一下前序和中序有什么特点,前序1,2,4,7,3,5,6,8 ,中序4,7,2,1,5,3,8,6;
有如下特征:
1、前序中左起第一位1肯定是根结点,我们可以据此找到中序中根结点的位置rootin;
2、中序中根结点左边就是左子树结点,右边就是右子树结点,即[左子树结点,根结点,右子树结点],
我们就可以得出左子树结点个数为int left = rootin - leftin;;
3、前序中结点分布应该是:[根结点,左子树结点,右子树结点];
4、根据前一步确定的左子树个数,可以确定前序中左子树结点和右子树结点的范围;
5、如果我们要前序遍历生成二叉树的话,下一层递归应该是:
左子树:root->left = pre_order(前序左子树范围,中序左子树范围,前序序列,中序序列);;
右子树:root->right = pre_order(前序右子树范围,中序右子树范围,前序序列,中序序列);。
6、每一层递归都要返回当前根结点root;
*/
public TreeNode dfs(int[] preOrder,int[] inorder,int preOrderleft,int preOrderright,int inOrderleft,int inOrderright){
if(inOrderleft>inOrderright||preOrderleft>preOrderright) return null;
//先序数组当前的首个元素对应根节点
int root = preOrder[preOrderleft];
//在中序遍历中
int middle = inOrderleft;
//在中序中查找root的位置:
//则在中序遍历左边的元素为左子树中元素,在中序遍历右边的的元素为右子树
for(int i=inOrderleft;i<=inOrderright;i++){
//在中序遍历中找到root的位置middle
//因为元素不重复,所以找到一个元素跳出循环即可
if(inorder[i]==root){
middle=i;
break;
}
}
//在左子树中元素个数
TreeNode node = new TreeNode(root);
//分支算法
//递归
node.left=dfs(preOrder,inorder,preOrderleft+1,middle-inOrderleft+preOrderleft,inOrderleft,middle-1);
node.right=dfs(preOrder,inorder,middle-inOrderleft+preOrderleft+1,preOrderright,middle+1,inOrderright);
return node;
}
}
=======
根据中序遍历与后序遍历确定树
package JDFS;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/4/29 0029 18:15
* 从中序与后序遍历序列构造二叉树
*/
public class Problem106 {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder.length==0||postorder.length==0) return null;
return buildTree(inorder,postorder,0,inorder.length-1,0,postorder.length-1);
}
public TreeNode buildTree(int[] inorder, int[] postorder,int l1,int h1,int l2,int h2){
if(l1>h1||l2>h2) return null;
//后序遍历根接地那的值(根节点的位置)
int root = postorder[h2];
//中序遍历中对应根节点的位置
int middle = l1;
//在中序遍历的位置找到根节点的位置:
//在根节点的左边的为左子树,在根节点的右面为右子树(这点在中序遍历中显而易见)
//根据左子树中节点的个数从而确定后序遍历中左子树的元素的位置
for(int i=l1;i<=h1;i++){
if(inorder[i]==root){
middle=i;
break;
}
}
TreeNode node = new TreeNode(root);
//左子树的位置
node.left=buildTree(inorder,postorder,l1,middle-1,l2,l2+middle-l1-1);
node.right=buildTree(inorder,postorder,middle+1,h1,l2+middle-l1,h2-1);
return node;
}
}