今天我们来一起来看一下二叉树的的递归遍历以及其他的相关应用。首先回顾一下二叉树的前序遍历。
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
Class Solution{
List<Integer> list = new ArrayList<Integer>();
public List<Integer> preorderTravel(TreeNode root){
if(root!=null){ //1
list.add(root.val); //2
preorderTravel(root.left); //3
preorderTravel(root.rigth); //4
}
return list;
}
}
为了更好地理解递归的终止条件,我们将1234这一部分代码改为如下:
if(root == null)
return list;
list.add(root.val);
preorderTravel(root.left);
preorderTravel(root.rigth);
在这部分代码中,我们将递归代码分为两部分。1.终止条件2.递归过程。所以在写关于二叉树的递归算法的时候我们可以从宏观来把握,将整棵树抽象成成根、左边一个节点、右边一个节点。然后对这种抽象后的树进行归算法的描述,一定注意结束条件的选取。若没有结束条件,程序将一直执行,内存溢出。
根据上面的描述,我们试一下在一棵树中查找是否存在节点key(伪代码)
boolean containKey(TreeNode root, int key){
if(root == null) return false;
if(root.val == key) return true;
if(containKey(root.left,key)||containKey(root.rigth,key))
return true;
return false;
}
抽象后来看,若一进来根节点为空,不可能存在节点等于key返回false。若根节点等于key,那么就存在该节点返回true就可以啦。若既不为空也不是当前节点,那么去判断,左子树或者右子树含有当前节点就返回true。若左右自述都没有返回false即可。
思路是不是很简单,利用这种思路来看几个编程练习。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) //根节点为空,树高为0
return 0;
int leftdepth = maxDepth(root.left);//计算左子树高度
int rightdepth = maxDepth(root.right);//计算右子树高度
//返回左子树和右子树中高度大的那一个,但不要忘记要计算当前节点高度,所以在左右子树高度基础上要+1
return Math.max(leftdepth+1,rightdepth+1);
}
}
那么我们按照原来的思路进行:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
int minleft = minDepth(root.left);
int minright = minDepth(root.right);
return Math.min(minleft,minright)+1;
}
}
看起来似乎没什么问题,最大该最小嘛。但是如果这样提交会出现问题:
这是为什么呢。计算树的高度,从底向上取最大值,直到最后没问题。但是最小值会有这样的问题,测试用例为12,代表根节点为1,做孩子为2,右节点为空。此时计算最小,算出来的根节点做孩子为1,右孩子为0。取最小+1,所以根节点的最低高度变为1。但是我们知道最小深度不能计算其右节点啦,只能计算其左子树的高度。
那我们改一下,判断一下左右子树是否为空是不是就可以了呢:
public int minDepth(TreeNode root) {
if(root == null) return 0;
if(root.left!=null)
int minleft = minDepth(root.left);
if(root.right!=null)
int minright = minDepth(root.right);
return Math.min(minleft,minright)+1;
}
这里需要考虑,这样定义minleft和minrigth是否合理。如果一棵树只有根节点,直接返回Math.min(minleft,minrigth)+1,这两个变量是啥?机器蒙掉了,所以不能这么定义变量啦。
那么我们把变量定义在两个if语句外面,值定义一个最小值,用最小值接受左子树和右子树的值。那如果同时用min接受,min会被一直覆盖,没有办法判断最小值到底是谁。所以我们定义min后取无穷,只有当左右子树返回的值比当前min小我们才更新。最后返回min+1即可。
又改完了,那现在OK了吗~~测试一下,如果只有一个根节点,min=最大值啦,if不执行直接返回min+1,发生了什么?明明高度为1,却返回了最大值+1。
public int minDepth(TreeNode root) {
if(root == null) return 0;
int min = Integer.MAX_VALUE;
if(root.left!=null)
min = Math.min(minDepth(root.left),min);
if(root.right!=null)
min = Math.min(minDepth(root.right),min);
return min+1;
}
所以在检查一下结束条件和逻辑,好像漏掉了左右都为空(其实也就是叶子结点)的情况。那么叶子结点的高度我们应该返回1.所以最终正确代码如下:
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
if(root.left==null&&root.right==null)
return 1;
int min = Integer.MAX_VALUE;
if(root.left!=null)
min = Math.min(minDepth(root.left),min);
if(root.right!=null)
min = Math.min(minDepth(root.right),min);
return min+1;
}
}
没什么可多说的了,类比交换两个数据即可。
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null) return null;
TreeNode tempnode = root.left;
root.left = root.right;
root.right = tempnode;
invertTree(root.left);
invertTree(root.right);
return root;
}
}
插播一下这个题目的非递归方法:
public TreeNode invertTree(TreeNode root) {
if(root==null) return null;
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode cur = queue.poll();
TreeNode temp = cur.left;
cur.left = cur.right;
cur.right = temp;
if(cur.left!=null)
queue.offer(cur.left);
if(cur.right!=null)
queue.offer(cur.right);
}
return root;
}
递归:
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p==null&&q==null) return true;
if(p==null&&q!=null||p!=null&&q==null) return false;
if(p.val!=q.val) return false;
if(isSameTree(p.left,q.left)&&isSameTree(q.right,p.right))
return true;
return false;
}
非递归:
public boolean check(TreeNode p, TreeNode q) {
// p and q are null
if (p == null && q == null) return true;
// one of p and q is null
if (q == null || p == null) return false;
if (p.val != q.val) return false;
return true;
}
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) return true;
if (!check(p, q)) return false;
// init deques
ArrayDeque<TreeNode> deqP = new ArrayDeque<TreeNode>();
ArrayDeque<TreeNode> deqQ = new ArrayDeque<TreeNode>();
deqP.addLast(p);
deqQ.addLast(q);
while (!deqP.isEmpty()) {
p = deqP.removeFirst();
q = deqQ.removeFirst();
if (!check(p, q)) return false;
if (p != null) {
// in Java nulls are not allowed in Deque
if (!check(p.left, q.left)) return false;
//都为空,或者都不为空且相等执行下一个if
if (p.left != null) {//两棵树的左子树相同,且都不为空
deqP.addLast(p.left);
deqQ.addLast(q.left);
}
if (!check(p.right, q.right)) return false;
if (p.right != null) {//两棵树的右子树相同,且都不为空
deqP.addLast(p.right);
deqQ.addLast(q.right);
}
}
}
return true;
}
将一棵树从根部分为左右两部分,判断左右两部分是否为镜像。判断左子树的左孩子与右子树的右孩子是否相等,左子树的右孩子跟右子树的左孩子是否相等。
递归:
public boolean isSymmetric(TreeNode root){
return isMirror(root,root);
}
public boolean isMirror(TreeNode root1, TreeNode root2){
if(root1==null && root2==null) return true;
if(root1==null||root2==null) return false;
return root1.val==root2.val&&isMirror(root1.left,root2.right)&&isMirror(root1.right,root2.left);
}
非递归:
public boolean isSymmetric(TreeNode root){
if(root==null) return true;
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
que.offer(root);
while(!que.isEmpty()){
TreeNode t1 = que.poll();
TreeNode t2 = que.poll();
if(t1==null&&t2==null) continue;
if(t1==null||t2==null) return false;
if(t1.val!=t2.val) return false;
que.offer(t1.left);
que.offer(t2.right);
que.offer(t1.right);
que.offer(t2.left);
}
return true;
}
提供两种递归方式,一种自下向上返回个数(左右子树总和+1),一种自上向下边遍历边计算节点个数。
class Solution {
//从下向上计算
public int countNodes(TreeNode root) {
if(root == null) return 0;
return countNodes(root.left)+countNodes(root.right)+1;
}
}
class Solution {
int count = 0;//边遍历边计算节点个数
public int countNodes(TreeNode root) {
if(root==null) return 0;
count++;
countNodes(root.left);
countNodes(root.right);
return count;
}
}
递归:
①自顶向下判断,判断当前根节点是否左右子树平衡。平衡后再计算左子树的左右孩子是否平衡。每次判断过程中都需要计算孩子的左右子树高度,其出现大量重复计算。
class Solution {
//从上向下计算
public boolean isBalanced(TreeNode root) {
if(root==null) return true;
if(Math.abs(deep(root.left)-deep(root.right))>1) return false;
return isBalanced(root.left)&&isBalanced(root.right);
}
public int deep(TreeNode node){
if(node == null) return 0;
**int deepleft = deep(node.left);
int deeprigth = deep(node.right);
return Math.max(deepleft+1,deeprigth+1);
//return Math.max(deepleft,deeprigth)+1;
}
}
注:最后两种返回方式差别很大,第一种效率99,第二种效率17
②改善自顶向下计算冗余,采用自底向上方式计算树的高度。需要定义额外的数据结构存储高度和真假值。
public class TreeInfo{
int height;
boolean isbanlance;
TreeInfo(int height,boolean isbanlance){
this.height = height;
this.isbanlance = isbanlance;
}
}
class Solution {
public boolean isBalanced(TreeNode root) {
return Judge(root).isbanlance;
}
public TreeInfo Judge(TreeNode root){
if(root==null) return new TreeInfo(-1,true);
TreeInfo left = Judge(root.left);
if(!left.isbanlance)
return new TreeInfo(-1,false);
TreeInfo right = Judge(root.right);
if(!right.isbanlance)
return new TreeInfo(-1,false);**
if(Math.abs(left.height-right.height)<2)
return new TreeInfo(Math.max(left.height,right.height)+1,true);
return new TreeInfo(-1,false);
}
}
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
if(root==null) return false;
if(root.left==null&&root.right==null&&root.val==sum)
return true;
if(hasPathSum(root.left,sum-root.val)||hasPathSum(root.right,sum-root.val))
return true;
return false;
}
}
这个题目中要注意返回条件,如果不对左右子树进行空的判断就不能保证其为叶子结点,就可能出现错误。如[5,null,1,null,null,4]sum=5.此时会返回true,因为根节点向右遍历的时候满足==5.但是这并不是根节点,所以要注意结束条件的判断。
class Solution {
int sum = 0;
public int sumOfLeftLeaves(TreeNode root) {
getSum(root,false);
return sum;
}
public void getSum(TreeNode root,boolean flag){
if(root!=null){
if(root.left==null&&root.right==null&&flag)
sum+=root.val;
getSum(root.left,true);
getSum(root.right,false);
}
}
}