关于二叉树的几个小知识点

835 阅读3分钟

如何定义树节点

// 这里定义一个树节点
// 包含:节点值、子左节点、子右节点
function TreeNode(val) {
    this.val = val;
    this.left = this.right = null;
}

如何计算一个树节点的深度

  • 定义:从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
var maxDepth = function (root) {
  // 节点为空则不计数
  if (root === null) return 0;
  
  // 递归一次计数一次
  // 并且记录当前子节点最大深度
  return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
};
  • 时间复杂度:我们每个结点只访问一次,因此时间复杂度为 O(n), 其中 n 是结点的数量。
  • 空间复杂度:在最糟糕的情况下,树是完全不平衡的,例如每个结点只剩下左子结点,递归将会被调用 N 次(树的高度),因此保持调用栈的存储将是 O(n)。但在最好的情况下(树是完全平衡的),树的高度将是 log(n)。因此,在这种情况下的空间复杂度将是 O(log(n))

如何判断一个树为平衡二叉树

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

  • 首先,需要获取一个节点的高度

下图摘自 leetcode

var getRootHeight = function (node) {
    // 节点为 null 则取消节点计数
    if (node === null) return -1;
    
    // 节点计数
    // 当前子节点高度最大值
    return 1 + Math.max(getRootHeight(node.left), getRootHeight(node.right));
}
  • 其次,则可以使用递归计算当前树是否为平衡二叉树
var isBalanced = function (root) {
  if (root == null) {
    // 节点为空则平衡
    return true;
  } else if (Math.abs(getRootHeight(root.left) - getRootHeight(root.right)) > 1) {
    // 节点高度差大于1,则不平衡
    return false;
  } else {
    // 递归比较子节点
    return isBalanced(root.left) && isBalanced(root.right);
  }
};
  • 时间复杂度:O(n),计算每棵子树的高度和判断平衡操作都在恒定时间内完成。
  • 空间复杂度:O(n),如果树不平衡,递归栈可能达到 O(n)

如何判断一个树为对称二叉树

  • 定义:
    • 它们的两个根结点具有相同的值。
    • 每个树的右子树都与另一个树的左子树镜像对称。

下图摘自 leetcode

对称二叉树

  • 首先,需要比较两个节点是否对称,如下思路:
    • 左右节点是否都有值;
    • 左右父节点值是否相等;
    • 左右节点子左右值是否刚好相反;
var isMirror = function (node1, node2) {
  // 节点同为空
  if (node1 === null && node2 === null) return true;
  // 节点其中之一为空
  if (node1 === null || node2 === null) return false;

  // 比较父节点值是否相等 
  // 比较子左右节点是否相反
  return node1.val === node2.val && isMirror(node1.left, node2.right) && isMirror(node1.right, node2.left);
};
  • 这个时候就能比较该树是否对称
var isSymmetric = function (root) {
  // 比较该树自己是否对称
  return isMirror(root, root);
};
  • 时间复杂度:O(n),因为我们遍历整个输入树一次,所以总的运行时间为 O(n),其中 n 是树中结点的总数。
  • 空间复杂度:递归调用的次数受树的高度限制。在最糟糕情况下,树是线性的,其高度为 O(n)。因此,在最糟糕的情况下,由栈上的递归调用造成的空间复杂度为 O(n)

树的遍历

  • 先序遍历
    • 访问根节点
    • 遍历左子树
    • 遍历右子树
var preorderTraversal = function (node, array) {
    if (!node) return array;
        
    // 先根节点    
    array.push(node.val);
    
    // 子左节点
    node.left && preorderTraversal(node.left, array);

    // 子右节点
    node.right && preorderTraversal(node.right, array);

    return array;
  };
  • 中序遍历
    • 遍历左子树
    • 访问根节点
    • 遍历右子树
var inorderTraversal = function (node, array) {
    if (!node) return array;
        
    // 先子左节点
    node.left && inorderTraversal(node.left, array);
    
    // 再根节点    
    array.push(node.val);
    
    // 子右节点
    node.right && inorderTraversal(node.right, array);

    return array;
  };
  • 后序遍历
    • 遍历左子树
    • 遍历右子树
    • 访问根节点
var postorderTraversal = function (node, array) {
    if (!node) return array;
        
    // 先子左节点
    node.left && postorderTraversal(node.left, array);

    // 子右节点
    node.right && postorderTraversal(node.right, array);
    
    // 再根节点    
    array.push(node.val);
    
    return array;
  };

励志