二叉树遍历算法

Published on

二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有的结点,使得每个结点被访问依次且仅被访问一次。

对于二叉树,有深度遍历广度遍历,深度遍历有前序、中序以及后序三种遍历方法,广度遍历即我们平常所说的层次遍历。

  • 创建二叉树
// 定义结点TreeNode类
class TreeNode {
  node
  left
  right

  constructor(node) {
    this.node = node
    this.left = null
    this.right = null
  }
}

// 前序遍历生成二叉树
const creatTree = (list) => {
  const n = list.length
  if (n === 0) {
    return null
  }
  const node = new TreeNode(list[0])
  // 分割数组
  const index = (n - 1) / 2 + 1
  const left = list.slice(1, index)
  const right = list.slice(index)
  node.left = creatTree(left)
  node.right = creatTree(right)
  return node
}

creatTree(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'])

zoom-img

  • 前序遍历(DLR)

先访问根结点,然后前序遍历左子树,再前序遍历右子树。

// 递归遍历
const getDLR = (tree, DLR) => {
  if (tree) {
    DLR.push(tree.node)
    getDLR(tree.left, DLR)
    getDLR(tree.right, DLR)
  }
  return DLR
}
getDLR(tree, [])

// 非递归遍历
const getDLR = (tree, DLR) => {
  if (tree) {
    const stack = [tree] // 将二叉树压入栈
    while (stack.length !== 0) {
      tree = stack.pop() // 取栈顶
      DLR.push(tree.node)
      // 先右后左,栈是先进后出
      if (tree.right) stack.push(tree.right) // 把右子树入栈
      if (tree.left) stack.push(tree.left) // 把左子树入栈
    }
  }
  return DLR
}
getDLR(tree, [])

// ['A','B','C','D','E','F','G','H','I']
  • 中序遍历(LDR)

中序遍历结点的左子树,然后访问根结点,再中序遍历右子树。

// 递归遍历
const getLDR = (tree, LDR) => {
  if (tree) {
    getLDR(tree.left, LDR)
    LDR.push(tree.node)
    getLDR(tree.right, LDR)
  }
  return LDR
}
getLDR(tree, [])

// 非递归遍历
const getLDR = (tree, LDR) => {
  if (tree) {
    const stack = [] // 建空栈
    // 如果栈不为空或结点不为空,则循环遍历
    while (stack.length !== 0 || tree) {
      //如果结点不为空
      if (tree) {
        stack.push(tree) // 将结点压入栈
        tree = tree.left // 将左子树作为当前结点
      } else {
        tree = stack.pop() // 将结点取出
        LDR.push(tree.node)
        tree = tree.right // 将右结点作为当前结点
      }
    }
  }
  return LDR
}
getLDR(tree, [])

// ['C','B','D','E','A','G','F','H','I']

非递归中序算法的思想就是,把当前节点入栈,然后遍历左子树,如果左子树存在就一直入栈,直到左子树为空,访问但前节点,然后让右子树入栈。

  • 后序遍历(LRD)

从左到右先叶子后结点的方式遍历左右子树,最后访问根结点。

// 递归遍历
const getLRD = (tree, LRD) => {
  if (tree) {
    getLRD(tree.left, LRD)
    getLRD(tree.right, LRD)
    LRD.push(tree.node)
  }
  return LRD
}
getLRD(tree, [])

// 非递归遍历
const getLRD = (tree, LRD) => {
  if (tree) {
    const stack = [tree] // 将二叉树压入栈
    let tmp = null // 定义缓存变量
    while (stack.length !== 0) {
      tmp = stack[stack.length - 1] // 将栈顶的值保存在tmp中
      // 如果存在左子树,node !== tmp.left && node !== tmp.righ 是为了避免重复将左右子树入栈
      if (tmp.left && tree !== tmp.left && tree !== tmp.right) {
        stack.push(tmp.left) // 将左子树结点压入栈
      } else if (tmp.right && tree !== tmp.right) {
        // 如果结点存在右子树
        stack.push(tmp.right) // 将右子树压入栈中
      } else {
        const out = stack.pop().node
        LRD.push(out)
        tree = tmp
      }
    }
  }
  return LRD
}
getLRD(tree, [])

// ['C','E','D','B','G','I','H','F','A']

这里使用了一个 tmp 变量来记录上一次出栈、入栈的结点。思路是先把根结点和左树推入栈,然后取出左树,再推入右树,取出,最后取根结点。

下面是用这个算法遍历二叉树的过程:

stepstacktmptreeout
initAnullA
1ABAA
2ABCBA
3ABCCC
4ABDBC
5ABDEDC
6ABDEEE
7ABDDD
8ABBB
9AFAB
10AFGFB
11AFGGG
12AFHFG
13AFHIHG
14AFHIII
15AFHHH
16AFFF
17AFHF
18AAA
  • 层次遍历

从根结点从上往下逐层遍历,在同一层,按从左到右的顺序对结点逐个访问。

const breadthTraversal = (tree, BRT) => {
  if (tree) {
    const queue = [tree] // 将二叉树放入队列
    while (queue.length !== 0) {
      tree = queue.shift() // 从队列中取出一个结点
      BRT.push(tree.node)
      // 如果存在左子树,将左子树放入队列
      if (tree.left) queue.push(tree.left)
      // 如果存在右子树,将右子树放入队列
      if (tree.right) queue.push(tree.right)
    }
  }
  return BRT
}
breadthTraversal(tree, [])

// ['A','B','F','C','D','G','H','E','I']

使用数组模拟队列,首先将根结点归入队列。当队列不为空时,执行循环:取出队列的一个结点,如果该节点有左子树,则将该节点的左子树存入队列;如果该节点有右子树,则将该节点的右子树存入队列。

以上就是本文的全部内容,感谢阅读。