​二叉树前序遍历的迭代实现

544 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

哈喽,我是前端西瓜哥。

二叉树的前序遍历(Pre-Order Traversal),是二叉树的深度优先遍历的其中一种方式,指的是 先访问根节点,再访问子树的遍历方式

递归实现

通常我们会使用递归的方式去实现前序遍历。实现如下:

function preOrder(root) {
  if (root === nullreturn// 递归结束条件

  // 这里放使用 root 的业务代码
  // 比如 console.log(root.val);
  preOrder(root.left);
  preOrder(root.right);
}

递归的优点是简单,只要局部正确了,那整体上也就正确了。

对于初学者来说比较难理解,因为它不符合我们习惯的思维模式。你不知道为什么这样写局部,整体就对了。你也不知道整体上具体发生了什么,因为里面全是套娃。

不过只要你写多了,就能拥有 “我写对了局部,整体虽然不是非常能推理出流程,但代码确实通过了测试用例” 的能力。

但也因为是递归实现,程序运行时会维护一个函数调用栈。在函数中调用其他函数时,临时保持当前函数的环境变量。

但程序提供的调用栈的大小是有限的,当二叉树非常深的时候,要保存的环境变量太多,就会导致栈溢出错误,让程序停止执行。

要解决栈溢出的问题,就需要将递归实现改为迭代实现

迭代实现

代码实现为:

function preorderTraversal(root) {
  const stack = [];

  while (root || stack.length) {
    while (root) {
      // 这里放使用 root 的业务代码
      // 比如 console.log(root.val);
      
      stack.push(root);
      root = root.left;
    }
    root = stack.pop();
    root = root.right;
  }
};

首先我们一路取左节点,将其入栈,直到没有左节点为止。这是模仿递归的 dfs(node.left)

遍历入栈的同时,我们其实拿到了每一个 “根节点”,在这个地方写上我们的业务逻辑。

while (root) {
  // 这里放使用 root 的业务代码
  // 比如 console.log(root.val);

  stack.push(root);
  root = root.left;
}

遍历到节点没有左节点后,我们就需要通过出栈的方式,拿到最后一个节点,然后使用它的右节点

出栈是为了将节点丢掉。因为这个节点的左节点已经访问过,并且即将访问右子树了,所以就不需要了。对比到递归实现,就是一个函数完全执行完后,销毁当前调用栈。

while (root || stack.length) {
  while (root) {
    // root 的左节点都入栈。
  }
  root = stack.pop();
  root = root.right;
}

这个右节点如果存在,重新作为新的起点,重复上述的步骤,直到当栈为空且 root 为 null 才结束。

这里继续循环的条件是 while (root || stack.length),stack.length 还好理解,即是否还有节点的右子树待访问。

root 则比较晦涩,在我们获取右节点时,这个右节点还没有入栈,但栈此时可能已经为空了。因此 root 是否为 null 也要纳入循环判断。

结尾

二叉树前序遍历的迭代实现,思路上其实和递归类似,都是通过栈的方式记录好经过的节点,在访问根节点之后,先访问所有的左节点,再访问右节点。

递归实现更简单易懂,但迭代实现解决了栈溢出的问题。

我是啥都写写的前端西瓜哥,欢迎关注我。

文章首发于我的公众号:前端西瓜哥