当前位置: 首页 > java >正文

二叉树的非递归遍历 | 秋招面试必备

文章目录

  • 前言
    • 基础设置
  • 前序遍历
  • 中序遍历
  • 后序遍历
    • 法一
    • 法二

前言

本文将详细介绍二叉树的非递归遍历的实现方法,利用迭代循环实现二叉树的不同遍历方式。
如果在开始前,已掌握递归遍历二叉树的方法,理解栈帧的建立和销毁过程,能够更容易上手迭代写法。

基础设置

下面的函数均为类BinaryTree的成员函数,_root为类的成员变量,在构造函数中初始化为二叉树的根节点。

二叉树节点声明如下:

struct TreeNode {TreeNode(int d = 1) : left(nullptr), right(nullptr), data(d){}TreeNode* left;TreeNode* right;int data;
};

前序遍历

前序遍历,即按照“根左右”顺序遍历二叉树。迭代法的核心是使用栈记录将要遍历到的树的节点,而栈具有“后进先出”的特性,所以为满足先访问左子树,在访问右子树的要求,则节点的入栈顺序为==“右左”==。
初学时,节点入栈的顺序,容易与递归遍历“根左右”顺序混淆。因为在循环体中,没有完全按照“右左根”顺序进行处理,依然是先处理了根节点。这就需要辩证的看待“根”这一概念,当前处理的根节点也可以是某一节点的左子树(右子树)。学习迭代遍历法时,选择性地规避“根”节点的概念,可能更好理解。

每次循环的栈顶,都为将要处理的根节点,然后再处理右子树(若不为空)、左子树(若不为空)。

void PreorderIt() {if (_root == nullptr) {return;}std::stack<TreeNode*> s;s.push(_root);while (!s.empty()) {TreeNode* cur = s.top();s.pop();std::cout << cur->data << ' ';if (cur->right != nullptr) {s.push(cur->right);}if (cur->left != nullptr) {s.push(cur->left);}}return;
}

也可以显示使用一个指针cur,以表示当前要处理的根节点。

void PreorderItV2() {if (_root == nullptr) {return;}std::stack<TreeNode*> s;TreeNode* cur = _root;while (!s.empty() || cur != nullptr) {if (cur == nullptr) {cur = s.top();s.pop();}std::cout << cur->data << ' ';if (cur->right != nullptr) {s.push(cur->right);}cur = cur->left;}return;
}

中序遍历

迭代法中序遍历处理节点的顺序与“左根右”顺序一致,处理所有的左子树,然后访问根节点,最后处理右子树。

void InorderIt() {if (_root == nullptr) {return;}TreeNode* cur = _root;std::stack<TreeNode*> s;while (!s.empty() || cur != nullptr) {// 入栈所有的左子树while (cur != nullptr) {s.push(cur);cur = cur->left;}// 访问根节点cur = s.top();s.pop();std::cout << cur->data << ' ';// 处理右子树cur = cur->right;}return;
}

后序遍历

法一

遵循“左右根”顺序,迭代法也需要先处理左子树、右子树,最后是根节点。但是左右子树需要经过根节点中转(会访问两次),为避免反复访问一侧子树的节点,我们可以加入一个哨兵节点dummy,记录已经处理过的子树,从而解决死循环的问题。

void PostorderIt() {if (_root == nullptr) {return;}TreeNode* cur = _root;TreeNode* dummy = nullptr;std::stack<TreeNode*> s;while (!s.empty() || cur != nullptr) {// 处理左子树while (cur != nullptr) {s.push(cur);cur = cur->left;}cur = s.top();// 右子树存在 且 没有访问过if (cur->right && cur->right != dummy) { // 处理右子树cur = cur->right;}else {std::cout << cur->data << ' ';dummy = cur;s.pop();cur = nullptr; // 一定需要置空,避免反复处理左子树}}
}

法二

后序遍历原为“左右根”,那么如果按照“根右左”的顺序访问节点并逆序输出,那么也能得到相同的效果。【两次逆序,负负得正】

void PostorderItV2() {if (_root == nullptr) {return;}std::vector<int> res;std::stack<TreeNode*> s;s.push(_root);while (!s.empty()) {TreeNode* cur = s.top();s.pop();res.push_back(cur->data);if (cur->left != nullptr) {s.push(cur->left);}if (cur->right != nullptr) {s.push(cur->right);}}std::reverse(res.begin(), res.end());for (auto num : res) {std::cout << num << ' ';}return;
}
http://www.xdnf.cn/news/19749.html

相关文章:

  • Redis分布式缓存
  • RabbitMQ消息堆积问题排查:concurrentConsumers 配置的坑与解决方案
  • js设计模式-职责链模式
  • More Effective C++ 条款24:理解虚拟函数、多继承、虚继承和RTTI的成本
  • VMWare ubuntu24.04安装(安装ubuntu安装)
  • 复杂PDF文档如何高精度解析
  • css3元素倒影效果属性:box-reflect
  • IsaacLab训练机器人
  • uni-app 实现做练习题(每一题从后端接口请求切换动画记录错题)
  • 国内免费低代码软件精选:四款工具助你快速开启数字化转型之路
  • 力扣72:编辑距离
  • windows docker(二) 启动存在的容器
  • 5招教你看透PHP开发框架的生态系统够不够“牛”?
  • 推荐一个论文阅读工具ivySCI
  • latex怎么写脚注:标共一声明,标通讯作者
  • 使用 Avidemux 去除视频的重复帧
  • 从实操到原理:一文搞懂 Docker、Tomcat 与 k8s 的关系(附踩坑指南 + 段子解疑)
  • 血缘元数据采集开放标准:OpenLineage Guides 在 Spark 中使用 OpenLineage
  • SpringBoot3中使用Caffeine缓存组件
  • 模版进阶及分离编译问题
  • ansible判断
  • 科学研究系统性思维的方法体系:数据分析模板
  • C语言:归并排序和计数排序
  • OCR识别在媒资管理系统的应用场景剖析与选择
  • 基于ZooKeeper实现分布式锁(Spring Boot接入)及与Kafka实现的对比分析
  • Pod自动重启问题排查:JDK 17 EA版本G1GC Bug导致的应用崩溃
  • Element Plus 表格表单校验功能详解
  • 【Web前端】JS+DOM来实现乌龟追兔子小游戏
  • 轻型载货汽车变速器设计cad+设计说明书
  • 【序列晋升】25 Spring Cloud Open Service Broker 如何为云原生「服务市集」架桥铺路?