用JavaScript搞定二叉树的前中后序遍历(迭代法 + 递归法)

952 阅读5分钟

一、前言

  1. 此篇是关于二叉树的深度遍历,想看广度遍历的可以看我的这篇文章-用JavaScript搞定二叉树的层序遍历
  2. 此篇将用递归和迭代法来遍历二叉树, 并且递归和迭代都是通用写法。迭代用的是标记法

二、遍历顺序说明

2.1 前序遍历顺序

2.2 中序遍历顺序

2.3后序遍历顺序

三、代码

  1. 递归没啥说的,有用到闭包。递归需要注意三个地方:递归单层逻辑、递归函数参数返回值、递归结束条件
  2. 迭代法:标记法,访问过的标记为true, 未访问过的标记为false;当栈弹出的是访问过的节点,输出到结果集;当栈弹出的是未访问过的节点,压栈(压栈顺序和前序遍历顺序相反),此次压栈注意要改变本身的状态,因为本身已经变成已访问节点了
  3. git代码链接

3.1 前序遍历代码

3.1.1 前序递归法

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var preorderTraversal = function(root) {
  // 结果集
  const res = []
  // 递归函数,参数node:节点
  const preorder = function (node) {
    // 递归结束条件
    if(node === null) return 
    
    // 单层递归逻辑:根节点的val加入结果集、处理左子树、处理右子树
    res.push(node.val) // res是自由变量,此处用到闭包
    node.left && preorder(node.left)
    node.right && preorder(node.right)
  }
  preorder(root)
  
  return res
};

3.1.2 前序迭代法

  • 标记法,访问过的标记为true, 未访问过的标记为false
  • 当栈弹出的是访问过的节点,输出到结果集
  • 当栈弹出的是未访问过的节点,右子树、左子树、本身进行依次压栈(压栈顺序和前序遍历顺序相反),此次压栈注意要改变本身的状态,因为本身已经变成已访问节点了
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
const preorderTraversal = function (root) {
  const res = []
  if(root === null) return res

  // 栈, 初始化,从根节点压栈,此时根节点未被访问,标记为false
  const stack = [{
    node: root, isSearched: false
  }]
  
  while (stack.length) {
    // 出栈
    const {node, isSearched} = stack.pop()

    if(isSearched) {
      // 如果是访问过的节点,输出到结果集
      res.push(node.val)
    } else {
      // 如果是未访问的节点,按照与前序遍历的相反方向压栈(右、左、根)
      // 右,未访问
      node.right && stack.push({node: node.right, isSearched: false})
      // 左,未访问
      node.left && stack.push({node: node.left, isSearched: false})
      // 根,已访问
      stack.push({node: node, isSearched: true})
    }

  }

  return res
}

3.2 中序遍历代码

3.2.1 中序递归法

**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
const inorderTraversal = function(root) {
  // 结果集
  const res = []

  // 递归函数,参数是节点,这里用到了闭包---res
  const inorder = function(node) {
    // 递归结束条件
    if(node === null) return

    // 单层递归的逻辑,左子树压栈、输出根节点值、右子树压栈
    node.left && inorder(node.left)
    res.push(node.val)
    node.right && inorder(node.right)
  }

  inorder(root)

  return res
};

3.2.2 中序迭代法

  • 标记法,访问过的标记为true, 未访问过的标记为false
  • 当栈弹出的是访问过的节点,输出到结果集
  • 当栈弹出的是未访问过的节点,右子树、本身、左子树进行依次压栈(压栈顺序和中序遍历顺序相反),此次压栈注意要改变本身的状态,因为本身已经变成已访问节点了
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
const inorderTraversal = function(root) {
  const res = []
  if(root === null) return res

  // 栈, 初始化,从根节点压栈,此时根节点未被访问,标记为false
  const stack = [{node: root, isSearched: false}]

  while (stack.length) {
    // 出栈
    const {node, isSearched} = stack.pop()

    if(isSearched) {
      // 如果是访问过的节点,输出到结果集
      res.push(node.val)
    } else {
      // 如果是未访问的节点,按照与中序遍历的相反方向压栈(右、根、左)
      // 右,未访问
      node.right && stack.push({node: node.left, isSearched: false})
      // 根,已访问
      stack.push({node: node, isSearched: true})
      // 左,未访问
      node.length && stack.push({node: node.right, isSearched: false})
    }
  }

  return res
};

3.3 后序遍历代码

3.3.1 后序递归法

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
const postorderTraversal = function(root) {
  // 结果集
  const res = []

  // 递归函数,参数是节点,这里用到了闭包---res
  const inorder = function(node) {
    // 递归结束条件
    if(node === null) return

    // 单层递归的逻辑,左子树处理、右子树处理、输出根节点值
    node.left && inorder(node.left)
    node.right && inorder(node.right)
    res.push(node.val)
  }

  inorder(root)

  return res
 }

3.3.2 后序迭代法

  • 标记法,访问过的标记为true, 未访问过的标记为false
  • 当栈弹出的是访问过的节点,输出到结果集
  • 当栈弹出的是未访问过的节点,本身、右子树、左子树进行依次压栈(压栈顺序和后序遍历顺序相反),此次压栈注意要改变本身的状态,因为本身已经变成已访问节点了
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
const postorderTraversal = function(root) {
  const res = []
  if(root === null) return res

  // 栈, 初始化,从根节点压栈,此时根节点未被访问,标记为false
  const stack = [{
    node: root, isSearched: false
  }]
  
  while (stack.length) {
    // 出栈
    const {node, isSearched} = stack.pop()

    if(isSearched) {
      // 如果是访问过的节点,输出到结果集
      res.push(node.val)
    } else {
      // 如果是未访问的节点,按照与前序遍历的相反方向压栈(根、右、左)
      // 根,已访问
      stack.push({node: node, isSearched: true})
      // 右,未访问
      node.right && stack.push({node: node.right, isSearched: false})
      // 左,未访问
      node.left && stack.push({node: node.left, isSearched: false})
    }

  }

  return res
}