算法和数据结构:二叉树的常见操作

184 阅读5分钟

以 leetcode 上常见的一些题为例进行说明。

二叉树的操作一般都需要用到其遍历方法,各种遍历方式一般都可以完成,同时也分为递归的写法和循环迭代的写法,本文优先使用前序遍历递归的写法,因为易于理解。如果不了解,可以看我上一篇博客。

合并二叉树

两颗树同步遍历即可,遇到有空的,直接返回另一个棵树的节点就即可。以前序遍历给出示例。

var mergeTrees = function(t1, t2) {
    //使用递归进行遍历,
    if(t1 && t2){
        t1.val = t1.val + t2.val
        t1.left = mergeTrees(t1.left, t2.left)
        t1.right = mergeTrees(t1.right, t2.right)
        return t1;
    }else if(!t1){
        return t2
    }else{
        return t1
    }
};

翻转二叉树

每个节点的操作动作是交换左右分支。

var invertTree = function(root) {
  if(root){
      //当前节点,左右交换
      var copy = root.left;
      root.left = root.right;
      root.right = copy;
      //左节点再进行
      invertTree(root.left);
      //右节点在进行
      invertTree(root.right);
  }
  return root;
};

二叉树的最大深度

递归,求当前节点为根节点的树的最大深度就是左右分支最大深度 + 1。原理就是每个节点都会因为在某一层中而使得自己所在路径的深度 + 1。

可能这里用层序遍历对层数进行记录的方法更好理解。

var maxDepth = function(root) {
    //到底了
    if (!root) return 0;
    //左边的深度
    var leftDepth = maxDepth(root.left);
    //右边的深度
    var rightDepth = maxDepth(root.right);
    //当前层的深度等于子深度+1
    return Math.max(leftDepth, rightDepth)+1; 
};

二叉树的最小深度

递归,求当前节点为根节点的树的最小深度就是左右分支最小深度 + 1。

var minDepth = function(root) {
    if(!root) return 0 ;
    let left = minDepth(root.left);
    let right = minDepth(root.right);
    if(left === 0 || right === 0) return left + right + 1;
    //如果有一个为1说明当前节点不是叶子,要继续往下找叶子。如果都是0,那么就返回1;

    return Math.min(left, right) + 1;
};

平衡二叉树判断

本题中,一棵高度平衡二叉树定义为:

>一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。

判断当前树,递归左右分支是否平衡,这里我们要用到上面的最大深度计算函数。

var isBalanced = function(root) {
    //空检测
    if(!root) return true;
    //左右子深度计算
    var leftDepth = maxDepth(root.left);
    var rightDepth = maxDepth(root.right);
    //左右深度差
    differ = Math.abs(leftDepth-rightDepth);
    if (differ > 1){
        return false;
    }
    else{
        return isBalanced(root.left) && isBalanced(root.right)
    }

};

对称二叉树判断

内部定义一个比较左右分支的递归函数即可,代码很好理解。

var isSymmetric = function(root) {
    if (!root) return true; 
    //递归
    var compare = function(left, right) {
        //空检测
        if (!left && !right) {
            return true;
        } 
        else if ((!left && right) || (left && !right)) {
            return false;
        } 
        else {
            if (left.val === right.val) {
                return compare(left.left, right.right) && compare(left.right, right.left);
            } 
            else {
                return false
            }
        }
    }   
    return compare(root.left, root.right);
};

 二叉树的直径

一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

首先想到的思路是,遍历每个节点,再计算其左右深度,左深度+右深度+1即可。但其实没有必要,因为计算深度的过程其实也是遍历的过程。

这里我们要用到上面的最大深度计算函数,但是要稍作修改。我们需要定义了一个全局变量,来记录当前遍历过程中出现的最大直径。
var max;  
var diameterOfBinaryTree = function(root) {
    //空或者只有一个节点检测
    if( !root || (!root.left && !root.right) ) return 0;
    max = 1;
    //求最大深度的过程中,每个节点都会递归到,直接对max进行更新即可
    maxDepth(root, max);
    return max ;
};

//最大深度
var maxDepth = function(root){
    //空检测
    if(!root) return 0;
    var leftDepth = maxDepth(root.left);
    var rightDepth = maxDepth(root.right);
    max = Math.max(leftDepth+rightDepth,max); //不是包含节点数,路径是边数
    return Math.max(leftDepth, rightDepth)+1;
};

路径总和

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。递归一把梭!

var hasPathSum = function(root, sum) {
    //空检测
    if(!root) return false;
    //叶子检测
    if(!root.left && !root.right) return sum === root.val;
    //左右子路径检测
    return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);

};

子树判断

需要额外定义一个判断是否是同一棵树的函数。

遍历+递归实现。

var isSubtree = function(s, t) {
    //递归遍历的
    if(s && t){
        return isSubtreeNode(s,t) || isSubtree(s.left, t) || isSubtree(s.right, t);
    }
    else if (!s && !t) {
        return true;
    }
    else {
        return false;
    }
};
var isSubtreeNode = function(node, t){
    if((!node && t) || (node && !t)) return false;
    if(!node && !t) return true;
    if(node.val === t.val){
        return isSubtreeNode(node.left, t.left) && isSubtreeNode(node.right, t.right);
    } else {
        return false;
    }
};

找到二叉树最后一行最左边的数

层序遍历即可达到目的。

var findBottomLeftValue = function(root) {
    //层序遍历 每层先右后左
    var levOrderTraversal = function(root) {
        if (!root) return [];
        let array = [root],
            current = null,
            result = [];
        while(array.length > 0){
            current = array.shift();
            result.push(current.val);
            //从右到左压入
            if (current.right) array.push(current.right);
            if (current.left) array.push(current.left);
        }
        return result;
    };
    let res = [];
    res = levOrderTraversal(root);
    return res[res.length-1];
};

二叉树中的最大路径和

递归,求左右最大路径 ,求出来更新结果即可。

var maxPathSum = function(root) {
    let result = -Infinity

    function maxLineSum(root){
        if(!root) return 0
        let left = Math.max(maxLineSum(root.left),0) //小于0 的路径,就不要了
        let right = Math.max(maxLineSum(root.right),0)
        result = Math.max(left + right + root.val, result)
        return Math.max(left, right) + root.val
    }

    maxLineSum(root)

    return result
};

二叉树转链表

利用递归的思想

  1. 将左边变成链表;

  2. 找到左边的tail;

  3. 将右边编程链表;

  4. 将root的左边置为null;

  5. 将左边的链表接到root的right;

  6. 将右边的链表接到左边链表的tail上。

    var flatten = function(root) { if(!root) return root

    let leftHead = flatten(root.left)
    
    //找到左子树的最右边的,也就是反中序遍历的第一步
    let leftTail = root.left
    //如果一开始就是null,那么head那里会出了,否则不会找到null的
    while(leftTail && leftTail.right){
        leftTail = leftTail.right
    }
    
    let rightHead = flatten(root.right)
    
    //清空左子树
    root.left =null
    
    //左边为null的情况
    if(leftHead){
        root.right = leftHead
        leftTail.right = rightHead
    }else{
        root.right = rightHead
    }
    
    return root
    

    };