数据结构 - 二叉树

173 阅读3分钟

定义

二叉树:一个空树或一个跟节点和两颗分别为左子树、右子树的二叉树组成

就是最多两个子节点的树

特性:

  1. 二叉树的第i层上,最多有 2^(i - 1)个节点,i >= 1
  2. 高度为k的二叉树,最多的节点数:2^(k) - 1, k >= 1
  3. 在任意一颗二叉数中,若叶子节点数为n_0,度为2的节点数是n_2,则:

$n_0 = n_2 + 1$

证明如下:假设度为1的节点数$n_1$,则 $n = n_0 + n_1 + n_2$ 在二叉数中,除根节点外,每个节点都存在一根链接父节点的线,假设线的总数是 B,则 $n = B + 1$ 这些线也是 度为1 和 度为2的节点链接子节点的线,那么 $B = n_1 + 2 * n_2 $带入上面

$n = n_1 + 2 * n_2 + 1$ $n_0 + n_1 + n_2 = n_1 + 2 * n_2 + 1$ 得出 $n_0 = n_2 + 1$

二叉树的遍历

  1. 广度遍历,从上往下,从左到右
  2. 深度遍历,深度遍历分为以下三种
    • 前序遍历:根左右
    • 中序遍历:左根右
    • 后序遍历:左右根

其中深度优先遍历指的的前序遍历


递归法实现三种深度遍历
// 前序遍历 根左右,递归法
const preorder = (root) => {
  if (root) {
    console.log('前序遍历:', root.val)
    preorder(root.left)
    preorder(root.right)
  }
}
// 中序遍历 左根右,递归法
const indorder = (root) => {
  if (root) {
    indorder(root.left)
    console.log('中序遍历:', root.val)
    indorder(root.right)
  }
}
// 后序遍历 左右根,递归法
const postorder = (root) => {
  if (root) {
    postorder(root.left)
    postorder(root.right)
    console.log('后序遍历:', root.val)
  }
}
非递归法实现三种深度遍历
// 前序遍历 根左右,非递归法
/**
 * 思路:
 * 1. 首先将root放到stack中
 * 2. 循环直至stack空
 * 3. stack pop出顶部节点,然后将期右节点,左节点push
 * 因为是 根左右,所以push的时候先push右再push左
*/
const preorder = (root) => {
  if (!root) {
    return
  }
  const stack = [root]
  while(stack.length > 0) {
    const node = stack.pop()
    console.log('前序遍历:', node.val)
    if (node.right) {
      stack.push(node.right)
    }
    if (node.left) {
      stack.push(node.left)
    }
  }
}
// 中序遍历 左根右,递归法
/**
 * 思路:
 * 利用栈stack 和 node 两个进行控制
 * 1. node = root
 * 2. 循环直至stack为空并且 node也为kong
 * 3. 通过node去走左边,直至左边为空,那么栈中保存的就是左节点
 * 3. stack pop 一个节点,然后访问一下,然后node = node.right
 * (访问到节点node的时候,表示node的左子树已经完成遍历,然后遍历右子树即可)
*/
const indorder = (root) => {
  const stack = []
  let node = root
  while(stack.length > 0 || node) {
    // 遍历左边
    if (node) {
      stack.push(node)
      node = node.left
    } else {
      // 左边没有了,访问一下,然后遍历右边
      node = stack.pop()
      console.log('中序遍历:', node.val)
      node = node.right
    }
  }
}
// 后序遍历 左右根,递归法
/**
 * 思路:
 * 后序遍历比中序遍历再复杂一点,因为需要左边访问之后再访问右边最后访问根,跟节点存在2此入栈,需要记录一下方向
 * 入栈的数据结构记录为: { node: 节点, direction: 访问方向 }
 * 1. pos 指针,用来访问 pos = root
 * 2. 直至stack空,并且pos为空停止循环
 * 3. 一直访问左子树,并入栈
*/
const postorder = (root) => {
  const stack = []
  let pos = root
  while(stack.length > 0 || pos) {
    // 一直访问左子树
    if (pos) {
      stack.push({ node: pos, direction: 'left' })
      pos = pos.left
    } else {
      // 左边访问完成,pop一下栈顶元素
      const { node, direction } = stack.pop()
      // 如果栈顶元素表示 direction === 'right'表示已经访问过右子树了,直接访问节点
      if (direction === 'right') {
        console.log('后序遍历:', node.val)
        // 注意,需要把pos置为空,进行下一个循环
        pos = null
      } else {
        // 访问右子树
        pos = node.right
        // 再次入栈,然后 方向设置为 right
        stack.push({ node, direction: 'right' })
      }
    }
  }
}
广度遍历
const bfs = (root) => {
  const queue = [root]
  while(queue.length > 0) {
    const node = queue.shift()
    console.log('深度遍历:', node.val)
    if (node.left) {
      queue.push(node.left)
    }
    if (node.right) {
      queue.push(node.right)
    }
  }
}
根据前序遍历和中序遍历可以确定一颗二叉树
const createTree = (preorder, inorder) => {
  if (preorder.length <= 0) {
    return null
  }
  const root = { val: preorder[0], left: null, right: null }
  let i = 0
  for (; i < inorder.length; i++) {
    if (inorder[i] === preorder[0]) {
      break
    }
  }
  root.left = createTree(preorder.slice(1, i + 1), inorder.slice(0, i))
  root.right = createTree(preorder.slice(i + 1), inorder.slice(i + 1))
  return root
}