二叉树的子结构、深度以及重建二叉树
目录
- 二叉树的深度
- 平衡二叉树
- 二叉树的子结构
- 二叉树的重建
- 总结
- 参考资料
序
二叉树相关的套路,除了四种遍历方式,还有很多的内容,有二叉树的深度,将一个数组构建成为一个二叉树。
今天接着搞定二叉树。
二叉树的深度
剑指offer第55-I题,Leetcode第104题:
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,
最长路径的长度为树的深度。
例如:给定二叉树[3,9,20,null,null,15,7]
,返会他的最大深度。
3
/ \
9 20
/ \
15 7
二叉树的深度,说白了就是二叉树有几层,因此可以使用层序遍历,统计有多少层即可:
使用层序遍历的方法:
public int maxDepth(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
TreeNode node;
int dep=1;
while (!queue.isEmpty()){
int len=queue.size();
for (int i=0;i<len;i++){
node=queue.remove();
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
dep++;
}
return dep;
}
- 时间复杂度:将整个树每个节点访问了两边即
O(n)
- 空间复杂度:额外使用了一个栈来保存状态
O(n)
除了层序遍历,考虑递归的方法,对于根节点,分别求出左子树的深度和右子树的深度,取他们的最大值,
然后+1即是这棵树的深度。而左子树右子树同样,则有递归的方法如下:
public int maxDepth(ThreeNode root){
//代表已经出树的结构
if(root==null) return 0;
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
时间复杂度:遍历了所有的树的节点,O(n)
空间复杂度:当树退化成单链,则递归的深度为n,因此空间复杂度O(n)
平衡二叉树
平衡二叉树定义:如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
剑指offer第55-II题,Leetcode第110题:
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。
平衡二叉树的概念讲的很清楚,要求任意一节点的左右子树的深度差不能超过1,那么分别求出每个子树的
左右子树的深度,求差即可判断:
public boolean isBalanced(TreeNode root){
if(root==null) return true;
//分别判断左子树和右子树是不是平衡树,然后判断整棵树是不是平衡树
return isBalanced(root.left)&&isBalanced(root.right)&&(Math.abs(maxDepth(root.left)-maxDepth(root.right))<2);
}
上面代码计算判断每一个节点开始的二叉树都会判断他的所有子树是不是平衡二叉树,进行了大量的重复计算,那如何省去这些重复的计算呢?
答案是从叶结点开始计算,判断每一个叶结点为根的子树,是平衡树则返回他的树高,如果不是则返回-1,然后对其父父节点为根的子树,使用返回的树高进行计算。
public boolean isBalanced2(TreeNode root){
return recur(root)!=-1;
}
public int recur(TreeNode root){
if (root==null) return 0;
int left=recur(root.left);
if(left==-1) return -1;
int right=recur(root.right);
if(right==-1) return -1;
return Math.abs(left-right)>1?-1:Math.max(left,right)+1;
}
二叉树的子结构
剑指offer第26题:
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构),B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/ \
4 5
/ \
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
这个题我第一反应,要用A树种的每一个节点作为A的某棵子树的根节点和B进行比较。
所以我觉得需要用个队列或者栈或者列表将遍历整个树得到的元素保存起来,再一一判断。
但是我已经遍历乐一遍了,在遍历的时候遍历到当前节点就对以当前节点为根的子树进行判断,不好吗?
public boolean isSubStructure(TreeNode A, TreeNode B){
if(A==null || B==null) return false;
return help(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B);
}
//从A的根和B的跟开始判断是不是一样的结构
//这句话不知道怎么描述
public boolean help(TreeNode A, TreeNode B){
if(B==null) reurn false;
if(A==null || A.val!=B.val) return false;
return help(A.left,B.left)&&help(A.right,B.right);
}
二叉树的重建
二叉树的重建,嗯。。。想起来挺简单,做起来挺不简单的。。。
剑指offer第7题,Leetcode第105题:
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如:
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下二叉树
3
/ \
9 20
/ \
15 7
怎么说,刚看到这个题目一脸茫然,好像大学学过,但是呢,,,嗯学的惨不忍睹。。。
- 首先:二叉树不管前序,中序还是后序遍历,得到的元素的个数,一定是一样的
- 其次:前序遍历的第一个节点一定是整个树的根
- 第三:中序遍历中,排在根节点前面的一定是左子树的节点,后面的一定是右子树的节点
- 第四:如果根节点存在左子树,那么前序遍历中,根节点后面跟的一定是左子树的值,数量和中序遍历中左子树的个数一样
- 最后,还有一句保证里面没有重复元素来保证我们在中序遍历中查找根节点的时候是唯一的。
至此,可以将中序便利的数组分为三部分,根节点,左子树和右子树,对于左右子树,他们又是一个完成的树结构,重复以上方法即可。
什么意思呢?对于上面的题目:
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
根据前序遍历可以得到根节点是3
将中序遍历的结果分为3部分 左子树[9],根节点[3],右子树[15,20,7]
3
/ \
9 [15,20,7]
在前序遍历中对应的[15,20,7]的遍历结果是[20,15,7]
重复上述过程则有
3
/ \
9 20
/ \
15 7
上代码:
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder==null||preorder.size()==0) return null;
int len=preorder.size();
Map<Integer,Integer> indexMap=new HashMap<>();
for(int i=0;i<len;i++){
indexMap.put(inorder[i],i);
}
}
public TreeNode buildTree(int[] preorder, int preStrat,int preEnd,
int[] inorder, int inStart, int inEnd,
Map<Integer,Integer> indexMap){
if(preStart>preend) return null;
int rootVal=preorder[preStart];
TreeNode root =new TreeNode(rootVal);
int rootIndex=indexMap.get(rootVal);
int leftNodes=rootIndex-instart;
int rigthNodes=inEnd-rootIndex;
root.left=buildTree(preorder,preStrat+1,preStrat+leftNodes,
inorder,inStart,rootIndex-1,
indexMap);
root.right=buildTree(preorder,preEnd-rightNodes+1,preEnd,
inorder,rootIndex+1,inEnd,
indexMap);
return root;
}
总结
对于树的各种操作分治思想是一个很奇妙的思想,要判断一整棵树,可以先判断左右子树,依次递归,注意递归退出的边界条件。
参考资料
- leetcode对应题号以及解析