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

二叉树的前序、中序、后序、层序遍历

参考内容:

五分钟让你彻底理解二叉树的非递归遍历

Python实现二叉树的非递归遍历

二叉树遍历——深度优先(前中后序)+广度优先(层序遍历)

构造二叉树

定义二叉树结构如下

struct node
{int data;node *left;node *right;
};

构造如下形态二叉树

node *init_tree()
{node *node1 = (node *)malloc(sizeof(node));node *node2 = (node *)malloc(sizeof(node));node *node3 = (node *)malloc(sizeof(node));node *node4 = (node *)malloc(sizeof(node));node *node5 = (node *)malloc(sizeof(node));node *node6 = (node *)malloc(sizeof(node));node *node7 = (node *)malloc(sizeof(node));node *node8 = (node *)malloc(sizeof(node));node1->data = 1;node2->data = 2;node3->data = 3;node4->data = 4;node5->data = 5;node6->data = 6;node7->data = 7;node8->data = 8;node1->left = node2;node1->right = node3;node2->left = node4;node2->right = node5;node3->right = node6;node5->left = node7;node5->right= node8;return node1;
}

前序遍历(递归)

前序遍历顺序为根左右。要遍历整个二叉树我们就需要遍历二叉树的每一个子树,对于任何一个子树它的遍历方式均为根左右顺序遍历。即所有子问题均与父问题除规模大小不同外,其余均相同。所以可以采用递归方式实现前序遍历。

// 前序遍历 根左右
void pre_order_traversal(node *root)
{if(root) {cout<<root->data<<" ";pre_order_traversal(root->left);pre_order_traversal(root->right);}
}

遍历结果为:1 2 4 5 7 8 3 6

中序遍历(递归)

中序遍历顺序为左根右。其与前序遍历仅顺序不同,其余均相同。

// 中序遍历 左根右
void in_order_traversal(node *root)
{if(root) {in_order_traversal(root->left);cout<<root->data<<" ";in_order_traversal(root->right);}
}

遍历结果为:4 2 7 5 8 1 3 6

后序遍历(递归)

后序遍历顺序为左右根。其与前序、中序遍历仅顺序不同,其余均相同。

// 后序遍历 左右根
void post_order_traversal(node *root)
{if(root) {post_order_traversal(root->left);post_order_traversal(root->right);cout<<root->data<<" ";}
}

遍历结果为:4 7 8 5 2 6 3 1

前序遍历方法一(非递归)

因为递归实际上是由系统帮我们进行压栈,所以理论上所有递归算法都可以改为循环+栈实现,那么我们先照着上述前序遍历的样子修改为循环+栈的形态。需要注意的是由于栈先进后出的特性,为了保证左孩子在右孩子前被访问,所以应该先右孩子入栈,再左孩子入栈。

// 前序遍历 根左右
void pre_order_traversal(node *root)
{stack<node *> s;s.push(root);while(!s.empty()) {node *cur = s.top();s.pop();if(cur) {cout<<cur->data<<" ";s.push(cur->right);s.push(cur->left);}}
}

遍历结果为:1 2 4 5 7 8 3 6

前序遍历方法二(非递归)

现在我们换一种思路来实现前序非递归遍历,仔细观察前序遍历的递归调用过程。

  1. 先把从根结点开始的所有左子树放入栈中;
  2. 弹出栈顶元素
  3. 如果栈顶元素有右子树,那么右子树入栈
  4. 重复上述过程直到栈为空

因此我们可以写出遍历代码

// 前序遍历 根左右
void pre_order_traversal(node *root)
{stack<node *> s;node *cur = root;while(cur || !s.empty()) {// 将左子树全部入栈while(cur) {cout<<cur->data<<" ";s.push(cur);cur = cur->left;}if(!s.empty()) {cur = s.top();s.pop();cur = cur->right;}}
}

遍历结果为:1 2 4 5 7 8 3 6

中序遍历(非递归)

有了前面的基础,我们再来考虑中序遍历,会发现中序遍历与前序遍历只是打印结点的位置不一样。前序遍历是在结点入栈时打印,中序遍历只需要替换为在结点出栈时打印即可。

// 中序遍历 左根右
void in_order_traversal(node *root)
{stack<node *> s;node *cur = root;while(cur || !s.empty()) {// 将左子树全部入栈while(cur) {s.push(cur);cur = cur->left;}if(!s.empty()) {cur = s.top();cout<<cur->data<<" ";s.pop();cur = cur->right;}}
}

遍历结果为:4 2 7 5 8 1 3 6

后序遍历方法一(非递归)

后序遍历相对来说显得更加复杂了。在前序和中序遍历中,只要左子树处理完毕实际上栈顶元素就可以出栈了,但后序遍历需要把左子树和右子树都处理完毕才能出栈,显然我们需要某种方法记录遍历的过程。

实际上我们只需要记录下遍历的前一个结点就能解决问题,因为通过前一个结点我们可以做如下判断:

  1. 如果前一个结点是当前结点的右子树,那么说明右子树已经遍历完毕可以出栈了
  2. 如果前一个结点是当前结点的左子树而且当前结点没有右子树,那么说明可以出栈了
  3. 如果当前结点即没有左子树也没有右子树,即为叶子结点,那么说明可以出栈了

若不属于上述情况,则依次将当前结点的右孩子和做孩子入栈,这样就能保证每次取栈顶元素时,左孩子都在右孩子前面被访问,左孩子和右孩子都在父结点前面被访问。

// 后序遍历 左右根
void post_order_traversal(node *root)
{stack<node *> s;node *pre = NULL;node *cur = root;s.push(cur);while(!s.empty()) {cur = s.top();// 叶子结点if((!cur->left && !cur->right) // 叶子结点|| pre == cur->right // 前一个结点为当前结点右子树|| (pre == cur->left && !cur->right)) { // 前一个结点为当前结点左子树,且没有右子树cout<<cur->data<<" ";pre = cur;s.pop();} else {if(cur->right)s.push(cur->right);if(cur->left)s.push(cur->left);}}
}

遍历结果为:4 7 8 5 2 6 3 1

后序遍历方法二(非递归)

后序遍历的顺序是左右根,如果把这个顺序倒过来就是根右左,是不是发现和前序遍历很像?那么我只需要按照根右左的方式遍历完,然后将遍历结果掉一个个儿就可以,而栈就具备掉个儿的功能,因此可写出如下代码。

// 后序遍历 左右根
void post_order_traversal(node *root)
{stack<node *> s;stack<int> ans;node *cur = root;while(cur || !s.empty()) {// 将左子树全部入栈while(cur) {ans.push(cur->data);s.push(cur);cur = cur->right;}if(!s.empty()) {cur = s.top();s.pop();cur = cur->left;}}while(!ans.empty()) {cout<<ans.top()<<" ";ans.pop();}
}

遍历结果为:4 7 8 5 2 6 3 1

层序遍历

层序遍历即广度优先遍历,使用队列即可实现。

// 层序遍历
void breadth_first_order_traversal(node *root)
{queue<node *> q;q.push(root);while(!q.empty()){node *cur = q.front();q.pop();if(cur){cout<<cur->data<<" ";q.push(cur->left);q.push(cur->right);}}
}

遍历结果为:1 2 3 4 5 6 7 8

相关文章:

  • 【深度学习】Yolov8 区域计数
  • HCIE-CCE
  • LeetCode热题100——链表
  • 【Mquant】6:构建价差套利(二)
  • .NET开源全面方便的第三方登录组件集合 - MrHuo.OAuth
  • 活用package.json脚本,用node拷贝文件到指定目录
  • AR眼镜硬件解决方案_AR/VR智能眼镜安卓主板芯片方案介绍
  • 计算机毕设 基于大数据的服务器数据分析与可视化系统 -python 可视化 大数据
  • 【数据结构】树与二叉树(四):满二叉树、完全二叉树及其性质
  • YOLOv5算法改进(22)— 更换主干网络MobileNetv3 + 添加CA注意力机制
  • KiKi知道了什么是质数,他现在想知道所有三位整数中,有多少个质数
  • viple进阶2:打印九九乘法表
  • SLAM从入门到精通(被忽视的基础图像处理)
  • STM32笔记—DMA
  • 2023年十大地推拉新接单平台和网推接单平台,都是一手单
  • 【划重点】MySQL技术内幕:InnoDB存储引擎
  • 【前端学习】-粗谈选择器
  • C++11: atomic 头文件
  • CentOS7简单部署NFS
  • JAVA SE 6 GC调优笔记
  • javascript从右向左截取指定位数字符的3种方法
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • PHP CLI应用的调试原理
  • Phpstorm怎样批量删除空行?
  • scrapy学习之路4(itemloder的使用)
  • SpringBoot几种定时任务的实现方式
  • 阿里云前端周刊 - 第 26 期
  • 简单数学运算程序(不定期更新)
  • 前端
  • 前端之Sass/Scss实战笔记
  • 使用parted解决大于2T的磁盘分区
  • 用jQuery怎么做到前后端分离
  • 自制字幕遮挡器
  • 东超科技获得千万级Pre-A轮融资,投资方为中科创星 ...
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • #{}和${}的区别?
  • #我与Java虚拟机的故事#连载19:等我技术变强了,我会去看你的 ​
  • (09)Hive——CTE 公共表达式
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (附源码)python旅游推荐系统 毕业设计 250623
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
  • (一)认识微服务
  • (转)shell调试方法
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .NET 8.0 中有哪些新的变化?
  • .NET的微型Web框架 Nancy
  • .NET上SQLite的连接
  • .skip() 和 .only() 的使用
  • /boot 内存空间不够
  • @data注解_一枚 架构师 也不会用的Lombok注解,相见恨晚
  • []FET-430SIM508 研究日志 11.3.31
  • [ASP.NET 控件实作 Day7] 设定工具箱的控件图标
  • [AutoSar]BSW_OS 02 Autosar OS_STACK