推导前序序列

已知二叉树的中序序列是ABCDEFG,后序序列是BDCAFGE,求前序序列。

思路

clipboard.png

二叉树的后序序列是按照「左子树」,「右子树」,「根」的顺序排列的,序列中最后一个元素代表该二叉树的根节点。
二叉树的前序序列是按照「根」,「左子树」,「右子树」的顺序排列的,序列中第一个元素代表该二叉树的根节点。故通过已知的后序序列可以发现根,作为前序序列的第一个元素。
二叉树的中序序列是按照「左子树」,「根」,「右子树」的顺序排列的,根将左子树的序列和右子树的序列分割在左右两边。通过后序序列发现根,找到其在中序序列的位置,从而在中序序列中发现左子树和右子树并获得它们的长度,最后在后序序列中找到对应的左子树和右子树的后序序列。
这样求某二叉树的前序序列,变成了求该二叉树左子树的前序序列和该二叉树右子树的前序序列。

递归解法

一棵树,先找到其根,将其压入栈中,再将其分割成左右子树,按左子树,右子树的顺序,找子树的根,将其压入栈中,直到子树不可分割,得到整棵树的前序序列。

/**
 * 返回二叉树的前序序列
 * @param {array} inOrder 
 * @param {array} postOrder 
 * @returns {array} preOrder
 */
function findPreOrder (inOrder, postOrder) {
  let preOrder = []
  // 保存前序序列
  !function findRoot(inOrder, postOrder) {
    if (inOrder.length <= 1) { 
      // 若序列的长度为0或1,停止递归调用
      inOrder[0] && preOrder.push(inOrder[0]) 
      // 若序列长度为1,则它就是该树的根节点,根入栈
      // 在先序序列中,树的根排列顺序先于子树的根
      return
    } else {
      const root = postOrder[postOrder.length - 1] 
      // 找到根节点
      preOrder.push(root) 
      // 根入栈
      const index = inOrder.indexOf(root) 
      // 找到根节点在中序序列中的位置
      const newInOrderLeft = inOrder.slice(0, index) 
      // 根据根的位置找出左子树的中序序列
      const newInOrderRight = inOrder.slice(index + 1) 
      // 根据根的位置找出右子树的中序序列
      const newPostOrderLeft = postOrder.slice(0, newInOrderLeft.length) 
      // 根据左子树中序序列的长度找出其后序序列
      const newPostOrderRight = postOrder.slice(newInOrderLeft.length, newInOrderRight.length + newInOrderLeft.length) 
      // 根据右子树中序序列的长度找出其后序序列
      findRoot(newInOrderLeft, newPostOrderLeft)
      // 找到左子树的根
      findRoot(newInOrderRight, newPostOrderRight)
      // 找到右子树的根
      // 在先序序列中,左子树的根排列顺序先于右子树的根
    }
  }(inOrder, postOrder)
  return preOrder
  // 返回前序序列
}

let preOrder = findPreOrder(Array.from('ABCDEFG'), Array.from('BDCAFGE'))
console.log(preOrder)
// [ 'E', 'A', 'C', 'B', 'D', 'G', 'F' ]

非递归解法

clipboard.png

非递归解法和递归解法的思路相同,找到根,压入栈,分割左右子树,找到它们的根,压入栈。
不同之处在于用inOrderArr保存待分割的树的中序序列,用postOrderArr保存待分割的树的后序序列。

const findPreOrder = (inOrder, postOrder) => {
  let preOrder = [],
  // 保存前序序列
    inOrderArr = [inOrder],
    // 存储待分割的树的中序序列,初始状态为传入的树的中序序列
    postOrderArr = [postOrder]
    // 存储待分割的树的后序序列,初始状态为传入的树的后序序列
  while (preOrder.length < inOrder.length) {
    let inOrderEle = inOrderArr.shift(),
    // 取出子树的中序序列
      postOrderEle = postOrderArr.shift()
      // 取出子树的后序序列
    if (inOrderEle.length <= 1) {
    // 若取出的序列的长度为0或1,就不再分割
      inOrderEle[0] && preOrder.push(inOrderEle[0])
      // 若取出的序列的长度为1,则为根,入栈
    } else {
      let root = postOrderEle[postOrderEle.length - 1]
      // 找到根
      preOrder.push(root)
      // 根入栈
      // 开始分割左右子树
      const index = inOrderEle.indexOf(root)
      const newinOrderEle1 = inOrderEle.slice(0, index)
      const newinOrderEle2 = inOrderEle.slice(index + 1)
      inOrderArr = [newinOrderEle1, newinOrderEle2, ...inOrderArr] // 用分割后的左右子树代替原先的树
      const newpostOrderEle1 = postOrderEle.slice(0, newinOrderEle1.length)
      const newpostOrderEle2 = postOrderEle.slice(newinOrderEle1.length, newinOrderEle1.length + newinOrderEle2.length)
      postOrderArr = [newpostOrderEle1, newpostOrderEle2, ...postOrderArr] // 用分割后的左右子树代替原先的树
    }
  }
  return preOrder
  // 返回前序序列
}

let preOrder = findPreOrder(Array.from('ABCDEFG'), Array.from('BDCAFGE'))
console.log(preOrder)
// [ 'E', 'A', 'C', 'B', 'D', 'G', 'F' ]

推导后序序列

已知二叉树的前序序列是EACBDGF,中序序列是ABCDEFG,求后序序列。

思路

通过前序序列找到根,找到根在中序序列的位置,将树分割成左右子树,按照先右子树再左子树的顺序找到子树的根,直至子树不可分割。先找到的根排在后找到的根的后面。
和推导前序序列,相比思路大致相同,实现细节上,找树的根的方式不同,分割子树的方式不同,保存根的方式不同。

递归解法

const findPostOrder = (preOrder, inOrder) => {
  let postOrder = []
  // 保存后序序列
  !function findRoot(preOrder, inOrder) {
    if(inOrder.length <= 1){
    // 若序列的长度为0或1,停止递归调用
      inOrder[0] && postOrder.unshift(inOrder[0])
      // 若序列长度为1,则它就是该树的根节点,根入栈
      // 在后序序列中,树的根排列顺序后于子树的根
      return
    }else{
      const root = preOrder[0]
      // 找到根节点
      postOrder.unshift(root)
      // 保存根
      const index = inOrder.indexOf(root)
      // 找到根节点在中序序列中的位置
      const newInOrderLeft = inOrder.slice(0, index)
      // 根据根的位置找出左子树的中序序列
      const newInOrderRight = inOrder.slice(index + 1)
      // 根据根的位置找出右子树的中序序列
      const newPreOrderLeft = preOrder.slice(1, newInOrderLeft.length + 1)
      // 根据左子树中序序列的长度找出其前序序列
      const newPreOrderRight = preOrder.slice(newInOrderLeft.length + 1)
      // 根据右子树中序序列的长度找出其前序序列
      findRoot(newPreOrderRight, newInOrderRight)
      // 找到右子树的根
      findRoot(newPreOrderLeft, newInOrderLeft)
      // 找到左子树的根
    }
  }(preOrder, inOrder)
  return postOrder
  // 返回后序序列
}

let postOrder = findPostOrder(Array.from('EACBDGF'), Array.from('ABCDEFG'))
console.log(postOrder)
// [ 'B', 'D', 'C', 'A', 'F', 'G', 'E' ]

非递归解法

非递归解法和递归解法的思路相同,找到根并保存,分割左右子树,找到它们的根并保存。
不同之处在于用preOrderArr保存待分割的树的前序序列,用inOrderArr保存待分割的树的中序序列,且每次取最后一个元素开始处理。

const findPostOrder = (preOrder, inOrder) => {
  let postOrder = [],
    preOrderArr = [preOrder],
    inOrderArr = [inOrder]
  while (postOrder.length < inOrder.length) {
    let inOrderEle = inOrderArr.pop(),
      preOrderEle = preOrderArr.pop()
    if (inOrderEle.length <= 1) {
      inOrderEle[0] && postOrder.unshift(inOrderEle[0])
    } else {
      let root = preOrderEle[0]
      postOrder.unshift(root)
      const index = inOrderEle.indexOf(root)
      const newinOrderEle1 = inOrderEle.slice(0, index)
      const newinOrderEle2 = inOrderEle.slice(index + 1)
      inOrderArr = [...inOrderArr, newinOrderEle1, newinOrderEle2]
      const newpreOrderEle1 = preOrderEle.slice(1, newinOrderEle1.length + 1)
      const newpreOrderEle2 = preOrderEle.slice(newinOrderEle1.length + 1)
      preOrderArr = [...preOrderArr, newpreOrderEle1, newpreOrderEle2]
    }
  }
  return postOrder
}

console.log(findPostOrder(Array.from('EACBDGF'), Array.from('ABCDEFG')))
// [ 'B', 'D', 'C', 'A', 'F', 'G', 'E' ]

推导中序序列

那是不可能的!

已知前序序列ABC,后序序列CBA,求中序序列。

这是一道多解题。

clipboard.png

推荐结合nodemon在node环境下学习算法。


nbb3210
436 声望31 粉丝

优雅地使用JavaScript解决问题