算法学习:(前端)常见二叉树的分类和判别

833 阅读5分钟

二叉树的遍历

关于二叉树的各种(递归,迭代,morris)遍历,前序,中序,遍历,请看我的前一篇的文章。(juejin.cn/post/703486…

判断是否为搜索二叉树

搜索二叉树是一种特殊有序的二叉树。满足下面的条件即为搜索二叉树:

  • 它的根节点左子树不为空,且它左子树上面的所有节点的值都小于它的根节点的值。
  • 它的右子树不为空,那么它右子树任意节点的值都大于他的根节点的值,
  • 它的左右子树也是二叉搜索树。 如果对搜索二叉树中序遍历,则可以得到一个有序的(从小到大)的序列。如图:

helanguoqi.webp

递归版本

递归思路:

  • 调用函数,参数传入上界和下届为系统最大值和最小值
  • 如果节点为空,判断为true,即为搜索二叉树
  • 判断当前节点是否都在上下届 (min, max) 的范围内
  • 递归判断节点的左树和右树是否为搜索二叉树,调用子函数的时候,根据当前节点的值作为边界值传给子函数(判断节点左树的函数中传入tree.val作为子函数的上界,判断节点右树的函数中传入tree.val作为子函数的下界)
function isSBT (tree, min, max) {
  if (!tree) {
    return true
  }
  return isBST(tree.left, min, tree.val) && isBST(tree.right, tree.val, max)  && (max > tree.val) && (min < tree.val)
}

isBST(tree, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER)

迭代版本

迭代版本实现基础基于上面二叉树的特点:如果对搜索二叉树中序遍历,则可以得到一个有序的(从小到大)的序列。 思路:

  • 定义一个val,为系统最小值
  • morris中序遍历,每次遍历到一个节点,判断当前节点值是否 > val,如果为false,则该二叉树不为搜索二叉树。如为true,更新val值,进如下一次循环
  • 遍历结束,返回true
function isSBT2 (tree) {
  let val = Number.MIN_SAFE_INTEGER
  let cur = tree
  function getMostRight (tree) {
    let cur = tree
    cur = cur.left
    while (cur && cur.right && cur.right !== tree) {
      cur = cur.right
    }
    return cur
  }
  let mostRight = null
  while (cur) {
    if (cur.left) {
      mostRight = getMostRight(cur)
      if (mostRight.right !== cur) {
        mostRight.right = cur
        cur = cur.left
        continue
      }
      if (val > cur.val) {
        return false
      }
      val = cur.val
      mostRight.right = null
    } else {
      if (val > cur.val) {
        return false
      }
       val = cur.val
    }
    cur = cur.right
  }
  return true
}

判断为完全二叉树

完全二叉树:非最后一层的节点都有左右子节点,在最后一层,并不是所有节点都有两个子节点,这类二叉树又称为完全二叉树(Complete Binary Tree),入下图: u=4259968463,3381704914&fm=26&fmt=auto.webp
在我们之前实现的堆结构中,就是用到了完全二叉树( juejin.cn/post/703145…
完全二叉树的判断,用层序遍历判断:

迭代版本

思路:

  • 定义一个变量 isLeaf 表示是否遍历到了叶节点
  • 层序遍历每个节点,当遇到节点有右子节点,没有左子节点,直接返回false,该树不为完全二叉树
  • 层序遍历每个节点,当遇到节点有左子节点,没有右子节点,isLeaf置为true
  • isLeaf = true 后,每个遍历到的节点必须是叶节点(无左右节点)
function isCBT (tree) {
  let queue = [tree]
  let isLeaf = false
  while (queue.length) {
    tree = queue.shift()
    if (tree.right && !tree.left) {
      return false
    }
    if (isLeaf && (tree.left || tree.right)) {
      return false
    }
    if (tree.left && tree.right) {
      queue.push(tree.left)
      queue.push(tree.right)
      continue
    }
    tree.left && queue.push(tree.left)
    !isLeaf && (isLeaf = true)
  }
  return true
}

判断为满二叉树

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
判断是否满二叉树,可以通过求得二叉树的高度h和节点数nums,满足nums = (2^k) -1即可。

递归版本

思路:

  • 递归函数返回结构,{nums: Number, height: Number},表示当前树的节点数和高度
  • 如果tree为空,返回{nums: 0, height: 1}
  • 分别求解左右子树
  • 组合当前节点的信息并返回
  • 根据nums = (2^k) -1 判断是否为满二叉树
function isFBT (tree) {
  function process (tree) {
    if (!tree) {
      return {nums: 0, height: 1}
    }
    const left = process(tree.left)
    const right = process(tree.right)
    return {
      nums: left.nums + right.nums + 1,
      height: left.height + 1,
    }
  }

  const res = process(tree)
  return res.nums === (Math.pow(2, res.height - 1) - 1)
}

迭代版本

思路:

  • morris前序遍历获取树的节点树(每遍历到一个节点,nums++)
  • morris前序遍历,会先遍历树的左边界,可以得到树的高度k
  • 根据nums = (2^k) -1 判断是否为满二叉树
function isFBT (tree) {
  let height = 1
  let cur = tree
  let flag = false
  let nodeNums = 0
  let mostRight = null
  function getMostRight (tree) {
    let cur = tree.left
    while (cur && cur.right && cur.right !== tree) {
      cur = cur.right
    }
    return cur
  }
  while (tree) {
    if (tree.left) {
      !flag && height++
      mostRight = getMostRight(tree)
      if (mostRight.right === tree) {
        mostRight.right = null
        tree = tree.right
      } else {
        nodeNums++
        mostRight.right = tree
        tree = tree.left
      }
    } else {
      flag = true
      nodeNums++
      tree = tree.right
    }
  }
  return nodeNums === (Math.pow(2, height) - 1)
}

判断平衡搜索二叉树

递归版本

function isBST(tree, min, max) {
  if (!tree) {
    return {
      isBST: true,
      height: 0
    }
  }
  const left = isBST(tree.left, min, tree.val)
  const right = isBST(tree.right, tree.val, max)
  return {
    isBST: left.isBST && right.isBST && (max > tree.val) && (min < tree.val) && (Math.abs(left.height - right.height) <= 1),
    height: Math.max(left.height, right.height) + 1
  }
}

二叉树leecode题目

    上面判断搜索二叉树,满二叉树,平衡搜索二叉树的递归版本都是二叉树dp的实现。
    顾名思义,树型动态规划就是在“树”的数据结构上的动态规划,平时作的动态规划都是线性的或者是建立在图上的,线性的动态规划有二种方向既向前和向后,相应的线性的动态规划有二种方法既顺推与逆推,而树型动态规划是建立在树上的,所以也相应的有二个方向: 

    1、叶->根:在回溯的时候从叶子节点往上更新信息

    2、根 - >叶:往往是在从叶往根dfs一遍之后(相当于预处理),再重新往下获取最后的答案。

    不管是 从叶->根 还是 从 根 - >叶,两者都是根据需要采用,没有好坏高低之分。树形DP简单理解,先确定递归函数的返回值结构,然后递归过程中,获取递归子函数(递归求左右子节点)的值,合并子函数返回结果加上对当前树的判断返回一个新的结果。 下面这道《 二叉树的最近公共祖先》,也可以用树形dp的思路求解。

leetcode 二叉树的最近公共祖先

var lowestCommonAncestor = function(root, p, q) {
     if (!root) {
        return null
      }
      if (root === p || root === q) {
        return root
      }
      const left = lowestCommonAncestor(root.left, p, q)
      const right = lowestCommonAncestor(root.right, p, q)
      if (left && right) {
        return left === right ? left : root
      }
      return left || right
};

剑指 Offer II 053. 二叉搜索树中的中序后继

思路: 分两种情况讨论:

  1. p节点有右节点,那么p的后继节点很快的可以求出(右树下的最左子节点)
  2. p节点没有右节点,只能从根节点开始遍历(遍历终止条件是遍历到当前p节点) 根据搜索二叉树的特点(所有的右树节点 > 当前节点,所有左树节点 < 当前节点)可以不断向下遍历,用一个变量保存 > P的最小节点即为答案。
var inorderSuccessor = function(root, p) {
    let cur = p.right
      if (cur) {
        while (cur && cur.left) {
          cur = cur.left
        }
        return cur
      }
      let child = root
      let inorderNode = null
      while (child !== p) {
        if (child.val > p.val) {
          inorderNode = !inorderNode || child.val < inorderNode.val ? child : inorderNode
        }
        child = p.val > child.val ? child.right : child.left
      
      }
      return inorderNode
};