返回

二叉树的三种遍历方法:前序、中序和后序 (JavaScript)<#

前端

二叉树遍历的非递归实现:前序、中序和后序

导语

探索二叉树遍历的非递归实现,解锁遍历这棵数据结构的强大方法。本文深入探讨前序、中序和后序遍历,并提供清晰的 JavaScript 代码示例,揭示遍历二叉树的奥秘。

前序遍历

什么是前序遍历?

前序遍历就像旅行者沿着一条路径,首先遇到的是根节点,然后是左子树,最后是右子树。

非递归实现

为了用 JavaScript 实现非递归的前序遍历,我们可以使用栈。就像栈里的盘子一样,我们将节点按顺序压入栈中。首先,将根节点压入。然后,依次弹出节点并访问它们。如果弹出节点有左子树,我们将左子树根压入栈中。同样,如果有右子树,我们将右子树根压入栈中。我们重复这个过程,直到栈空,遍历完所有节点。

function preorderTraversal(root) {
  const stack = [];
  const result = [];
  if (root) {
    stack.push(root);
  }
  while (stack.length > 0) {
    const node = stack.pop();
    result.push(node.val);
    if (node.right) {
      stack.push(node.right);
    }
    if (node.left) {
      stack.push(node.left);
    }
  }
  return result;
}

中序遍历

什么是中序遍历?

中序遍历就像一个谨慎的探索者,先探索左子树,然后访问根节点,最后探索右子树。

非递归实现

对于中序遍历,我们再次使用栈。这次,我们按不同的顺序压入和弹出节点。首先,我们将根节点压入栈中。然后,我们不断弹出栈顶节点,并访问它们。如果弹出节点有左子树,我们将左子树根压入栈中。我们重复这个过程,直到栈空。然后,访问根节点。最后,如果有右子树,我们将右子树根压入栈中,继续遍历。

function inorderTraversal(root) {
  const stack = [];
  const result = [];
  let node = root;
  while (stack.length > 0 || node) {
    if (node) {
      stack.push(node);
      node = node.left;
    } else {
      node = stack.pop();
      result.push(node.val);
      node = node.right;
    }
  }
  return result;
}

后序遍历

什么是后序遍历?

后序遍历就像一个有条不紊的后勤人员,首先遍历左子树,然后遍历右子树,最后访问根节点。

非递归实现

实现后序遍历需要两个栈。我们称它们为栈 1 和栈 2。首先,将根节点压入栈 1。然后,我们依次弹出栈 1 中的节点,并将其压入栈 2。如果弹出节点有左子树,我们将左子树根压入栈 1。同样,如果有右子树,我们将右子树根压入栈 1。我们重复这个过程,直到栈 1 空。然后,依次弹出栈 2 中的节点,并访问它们。

function postorderTraversal(root) {
  const stack1 = [];
  const stack2 = [];
  const result = [];
  if (root) {
    stack1.push(root);
  }
  while (stack1.length > 0) {
    const node = stack1.pop();
    stack2.push(node);
    if (node.left) {
      stack1.push(node.left);
    }
    if (node.right) {
      stack1.push(node.right);
    }
  }
  while (stack2.length > 0) {
    const node = stack2.pop();
    result.push(node.val);
  }
  return result;
}

总结

掌握了非递归二叉树遍历,我们赋予了计算机探索和访问二叉树节点的能力。前序、中序和后序遍历为我们提供了不同视角,帮助我们以高效和有意义的方式遍历这棵数据结构。

常见问题解答

  1. 为什么需要非递归遍历?

    • 非递归遍历不需要在函数调用栈中保留函数调用的状态,这使得它更节省内存,尤其是在处理大二叉树时。
  2. 如何处理空节点?

    • 在代码中,我们使用 if 语句检查节点是否存在。如果节点为空,我们将跳过它。
  3. 可以同时使用多个栈吗?

    • 是的,像后序遍历这样的某些遍历需要使用多个栈来保持节点的正确顺序。
  4. 这些遍历算法的时间复杂度是多少?

    • 所有这三种遍历算法的时间复杂度都是 O(n),其中 n 是二叉树中的节点数。
  5. 还有其他类型的二叉树遍历吗?

    • 是的,除了前序、中序和后序遍历外,还有其他类型的遍历,例如层次遍历和宽度优先搜索。