二叉树

98 阅读3分钟

普通二叉树

二叉树节点的一般定义

class TreeNode {
  constructor(val) {
    this.val = val
    this.left = null
    this.right = null
  }
}

二叉树的遍历:以某种方式访问二叉树的全部节点

// 先序遍历
function tranverse(root) {
  // root操作
  tranverse(root.left)
  tranverse(root.right)
}
// 中序遍历
function tranverse(root) {
  tranverse(root.left)
  // root操作
  tranverse(root.right)
}
// 后序遍历
function tranverse(root) {
  tranverse(root.left)
  tranverse(root.right)
  // root操作
}

二叉树的技巧

多利用函数的入参和返回值 如:求二叉树最大深度,

// 利用函数返回值
var maxDepth = function(root) {
  if (!root) return 0
  const left = maxDepth(root.left)
  const right = maxDepth(root.right)
  return 1 + Math.max(left, right)
};

// 利用函数参数
var maxDepth = function(root) {
  let res = 0
  const help = (node, depth) => {
    if (!node) {
      res = Math.max(res, depth)
      return
    }
    help(node.left, depth + 1)
    help(node.right, depth + 1)
  }
  help(root, 0)
  return res
};

二叉树是否相等

var isSameTree = function(p, q) {
  if (!p || !q) return p === q
  return p.val === q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right)
};

二叉树是否对称

需要注意在help函数中,需要p.left和q.right比对,p.right和q.left比对。

var isSymmetric = function(root) {
  if (!root) return true
  const help = (p, q) => {
    if (!p || !q) return p === q
    return p.val === q.val && help(p.left, q.right) && help(p.right, q.left)
  }
  return help(root.left, root.right) 
};

平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。

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

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

function getTreeHeight(root) {
  if (!root) return 0
  return 1 + Math.max(getTreeHeight(root.left), getTreeHeight(root.right))
}

var isBalanced = function(root) {
  if (!root) return true
  const left = getTreeHeight(root.left)
  const right = getTreeHeight(root.right)
  return Math.abs(left - right) <= 1 && isBalanced(root.left) && isBalanced(root.right)
};

优化解法:

  • 当左右两颗子树的高度差大于1时,提前返回,避免无效计算
  • 返回特殊值-1,表示树是不平衡的
function getTreeHeight(root) {
  // 当左右两颗子树的高度差大于1时,提前返回特殊值-1,表示树是不平衡的
  if (!root) return 0
  const lh = getTreeHeight(root.left)
  if (lh === -1) return -1
  const rh = getTreeHeight(root.right)
  if (rh === -1 || Math.abs(lh - rh) > 1) return -1
  return 1 + Math.max(lh, rh)
}

var isBalanced = function(root) {
  return getTreeHeight(root) !== -1
};

二叉树的右视图

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

  • 遍历树时,需要优先遍历树的右节点
  • 需要记住当前depth是否已经保存了节点,每个depth中,只有最右边的节点需要保存
var rightSideView = function(root) {
  const cache = new Map()
  const help = (node, depth) => {
    if (!node) return
    if (!cache.has(depth)) {
      cache.set(depth, node)
    }
    help(node.right, depth + 1)
    help(node.left, depth + 1)
  }
  help(root, 0)
  return Array.from(cache.values()).map(node => node.val)
};

二叉树的最近公共祖先

/**
 * 当前节点情况
 * - 为空:返回空
 * - 为p:返回p
 * - 为q:返回q
 * - 其他情况
 *  - p,q在左右子树中:返回当前节点
 *  - p,q在左子树:返回左子树结果
 *  - p,q在右子树:返回右子树结果
 *  - p,q左右子树均不在:返回空
 */
var lowestCommonAncestor = function(root, p, q) {
  if (root === null || root === p || root === q) return root
  const left = lowestCommonAncestor(root.left, p, q) 
  const right = lowestCommonAncestor(root.right, p, q)
  if (left && right) return root
  if (left) return left
  return right
};

二叉树的层序遍历

var levelOrder = function(root) {
  const res = [], nodes = []
  if (root) nodes.push(root)
  while (nodes.length) {
    const cur = []
    const length = nodes.length
    for (let i = 0; i < length; i++) {
      const node = nodes.shift()
      cur.push(node.val)
      if (node.left) nodes.push(node.left)
      if (node.right) nodes.push(node.right)
    }
    res.push(cur)
  }
  return res
};

二叉树的锯齿形遍历

var zigzagLevelOrder = function (root) {
  const res = [],
    nodes = []
  if (root) nodes.push(root)
  let flag = false
  while (nodes.length) {
    const cur = [],
      length = nodes.length
    for (let i = 0; i < length; i++) {
      const node = nodes.shift()
      cur.push(node.val)
      if (node.left)  nodes.push(node.left)
      if (node.right) nodes.push(node.right)
    }
    if (flag) cur.reverse()
    flag = !flag
    res.push(cur)
  }
  return res
}

二叉搜索树

二叉搜索树:任意节点的值大于所有左子节点的值,且要小于所有右子节点的值。其中序遍历的结果是有序的

二叉搜索树遍历框架

function tranverse(root, target) {
  if (root.val === target) {
    // root操作
  }
  if (root.val > target) {
    tranverse(root.left, target)
  }
  if (root.val < target) {
    tranverse(root.right, target)
  }
}

二叉搜索树有效性判断

// 前序遍历,首先判断根节点是否满足区间条件,然后依次判断左右子节点
var isValidBST = function(root, min = -Infinity, max = Infinity) {
  if (!root) return true
  const val = root.val
  return val > min && val < max && isValidBST(root.left, min, val) && isValidBST(root.right, val, max)
};

// 中序遍历,对应的节点是一个升序数组
// 将节点值存入数组再依次判断,占用O(n)空间
// 但其实只需要保存前一个节点值即可
var isValidBST = function(root) {
  let pre = -Infinity
  const help = node => {
    if (!node) return
    help(node.left)
    if (node.val > pre) {
      pre = node.val
    } else {
      pre = Infinity
    }
    help(node.right)
  }
  help(root)
  return pre !== Infinity
};

// 后序遍历,将左右子树的取值范围向上传递,判断根节点是否在合理的范围内
var isValidBST = function(root) {
  // help函数返回当前数的最小值和最大值
  const help = node => {
    // [Infinity, -Infinity]确保node.val <= l_max || node.val >= r_min不成立
    if (!node) return [Infinity, -Infinity]
    const [l_min, l_max] = help(node.left)
    const [r_min, r_max] = help(node.right)
    //[-Infinity, Infinity] 表示非二叉搜索树,且可以保证将此结果一直上传,直到函数入口
    if (node.val <= l_max || node.val >= r_min) return [-Infinity, Infinity]
    return [Math.min(l_min, node.val), Math.max(r_max, node.val)]
  }
  return help(root)[0] === -Infinity ? false : true
};

二叉搜索树的最近公共祖先

/**
 * 当前节点情况
 * - 为空:返回空(无此分支)
 * - 为p:返回p
 * - 为q:返回q
 * - 其他情况
 *  - p,q在左右子树中:返回当前节点
 *  - p,q在左子树:返回左子树结果
 *  - p,q在右子树:返回右子树结果
 *  - p,q左右子树均不在:返回空(无此分支)
 */
var lowestCommonAncestor = function(root, p, q) {
  if (root.val > Math.max(p.val, q.val)) {
    return lowestCommonAncestor(root.left, p, q)
  } else if (root.val < Math.min(p.val, q.val)) {
    return lowestCommonAncestor(root.right, p, q)
  }
  return root
};

二叉搜索树增、删、改、查

// 在BST中查找一个数是否存在
function isInBST(root, target) {
  if(root === null) return false
  if(root.val === target) return true
  if(root.val > target) return isInBST(root.left)
  if(root.val < target) return isInBST(root.right)
}

// 在BST中插入一个数
function insertIntoBST(root, val) {
  if(root === null) return new TreeNode(val)
  if(root.val === val) return root
  if(root.val > target) {
    root.left = insertIntoBST(root.left, val)
  }
  if(root.val < target) {
    root.right = insertIntoBST(root.right, val)
  }
  return root
}

// 在BST中删除一个数
function deleteNode(root, val) {
  function getMin(node) {
    while(node.left !== null) {
      node = node.left
    }
    return node
  }
  if(root === null) return null
  if(root.val === val) {
    if(root.left === null) return root.right
    if(root.right === null) return root.left
    // 使用右子树中的最小值替换当前节点
    const minNode = getMin(root.right)
    root.val = minNode.val
    root.right = deleteNode(root.right, minNode.val)
  }
  if(root.val > target) {
    root.left = deleteNode(root.left, val)
  }
  if (root.val < target) {
    root.right = deleteNode(root.right, val)
  }
  return root
}

满二叉树

满二叉树:每一层节点都是满的(叶子节点无子节点,非叶子节点均有两个子节点),又称完美二叉树 Perfect Binary Tree.

1094457-20170225183610632-1388959691.png

完全二叉树

完全二叉树:从根结点到倒数第二层满足完美二叉树,最后一层可以不完全填充,其叶子结点都靠左对齐,Complete Binary Tree

1094457-20170225183236538-961758163.png

完全二叉树节点数统计:

function countNodes(root) {
  let l = root, r = root
  let hl = 0, hr = 0
  while(l !== null) {
    l = l.left
    hl++
  }
  while(r !== null) {
    r = r.right
    hr++
  }
  if (hl === hr) {
    return Math.pow(2, hl) - 1
  }
  return 1 + countNodes(root.left) + countNodes(root.right)
}