使用递归形式以及迭代形式实现树的前中后序遍历

news2024/11/17 13:27:23

  相信大家对于二叉树的遍历并不陌生,对于二叉树的递归遍历我们也可以信手拈来。但是如果让我们将二叉树修改成为非递归的形式呢?是不是有点疑惑了?那么本次博客我们就来梳理一下二叉树的非递归遍历。

  由于递归遍历二叉树的代码以及逻辑都很简单,所以我们直接给出代码的结果,之后直接进行输出结果的比较即可。

  我们手动构建的树的形式如下图所示。

  

  遍历结果如下:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//创建一个节点,用于创建树的节点
typedef struct BTNodeTree
{
	int _val;
	struct BTNodeTree* left;
	struct BTNodeTree* right;
	BTNodeTree(int data)
		:_val(data)
		,left(nullptr)
		,right(nullptr)
	{}
}*BTNode;

//使用递归的形式实现二叉树的前中后序遍历
void preOrder(BTNode root)
{
	if (root == nullptr)
	{
		return;
	}
	cout << root->_val << " ";
	preOrder(root->left);
	preOrder(root->right);
}

void midOrder(BTNode root)
{
	if (root == nullptr)
	{
		return;
	}
	midOrder(root->left);
	cout << root->_val << " ";
	midOrder(root->right);
}

void posOrder(BTNode root)
{
	if (root == nullptr)
	{
		return;
	}
	posOrder(root->left);
	posOrder(root->right);
	cout << root->_val << " ";
}
BTNode& createTree()
{
	//由于我们从堆当中申请空间,在此函数结束的时候,并不会释放栈帧,因此可以使用&返回
	BTNode data1 = new BTNodeTree(1);
	BTNode data2 = new BTNodeTree(2);
	BTNode data3 = new BTNodeTree(3);
	BTNode data4 = new BTNodeTree(4);
	BTNode data5 = new BTNodeTree(5);
	BTNode data6 = new BTNodeTree(6);
	BTNode data7 = new BTNodeTree(7);
	BTNode data8 = new BTNodeTree(8);
	BTNode data9 = new BTNodeTree(9);
	BTNode data10 = new BTNodeTree(10);
	data1->left = data2;
	data1->right = data3;
	data2->left = data4;
	data2->right = data5;
	data3->left = data6;
	data3->right = data7;
	data4->left = data8;
	data4->right = data9;
	data5->left = data10;
	return data1;
}
int main()
{
	BTNode root = createTree();
	cout << "preOrder:";
	preOrder(root);
	cout << endl;
	cout << "midOrder:";
	midOrder(root);
	cout << endl;
	cout << "posOrder";
	posOrder(root);
	cout << endl;
	return 0;
}

     使用栈编写迭代形式的树的遍历

  之后的内容就是我们本次博客的重头戏了。我们需要将上述的过程修改成为非递归的形式。(为了便于区分我们可以将递归的形式遍历树的操作封装到一个命名空间域当中,避免访问冲突的情况产生)根据我们所学的知识来说:所有的递归函数都会创建一个栈帧,之后以栈的形式进行访问并输出数据,以达到我们的预期效果,所以我们就可以使用栈这个数据结构模拟实现我们栈帧的开辟。

   先序遍历

  首先是我们的先序遍历。首先我们需要创建一个栈,之后根据我们的输出要求向栈当中压入数据。首先我们向栈中压入我们的首节点,之后根据栈是否为空进行判断是否需要继续循环。之后我们因为需要先读取左子树当中的数据,因此我们栈顶的元素应该是左子树的数据节点,那么也就是说我们需要先向栈当中加入右子树的节点。之后每一次进行循环的时候,从栈当中拿出一个数据,删除并加入其左右子树即可。过程如图所示:

  根据上述步骤我们可以编写出如下的代码:

void preOrder(BTNode root)
{
	stack<BTNode> ret;
	ret.push(root);
	while (!ret.empty())
	{
		cout << ret.top()->_val << " ";
		BTNode tmp = ret.top();
		ret.pop();
		if (tmp->right)
		{
			ret.push(tmp->right);
		}
		if (tmp->left)
		{
			ret.push(tmp->left);
		}
	}
}
   中序遍历

  想要进行中序遍历操作,我们需要先清楚中序遍历执行遍历节点的步骤。首先我们需要先访问左子树,当左子树已经访问完毕之后才访问根节点进行打印数据。因此我们就需要设置一个指针进行遍历操作。首先我们从根节点开始,向左进行执行,如果左子树不为空就将该节点压入栈中继续访问,形成循环,当我们的左子树为空的时候,我们就需要将栈顶的元素赋值给cur,打印输出并访问右子树。过程如图所示:

  根据上述步骤我们可以编写出如下代码:

void midOrder(BTNode root)
{
	stack<BTNode> ret;
	BTNode cur = root;
	while (cur != nullptr || !ret.empty())
	{
		if (cur)
		{
			ret.push(cur);
			cur = cur->left;
		}
		else
		{
			cur = ret.top();
			ret.pop();
			cout << cur->_val << " ";
			cur = cur->right;
		}
	}
}
  后序遍历

  对于后序遍历的实现我们有两种方式。

    后序遍历1:

  我们可以根据后序遍历的特点进行观察,后序遍历操作会先访问左子树,之后再访问右子树,等到所有的节点都访问完毕的时候我们才会输出相应的数据。所以我们的执行顺序就为:左右根

  而我们的前序遍历,我们需要进行的遍历顺序为:根左右。那是不是说我们只需要对前序遍历进行略微的修改,再进行逆序就可以得到我们后序遍历的执行效果呢?我们可以尝试编写代码:

  需要注意的是:对于前序遍历我们需要修改加入栈中的节点的顺序,如果想要逆序得到左右根的话,我们正序就需要是根右左,我们就需要将栈顶元素时刻保持为右子树的状态,这是和前序遍历的唯一区别。

void posOrder1(BTNode root)
{
	vector<int> data;
	stack<BTNode> ret;
	ret.push(root);
	while (!ret.empty())
	{
		data.push_back(ret.top()->_val);
		BTNode tmp = ret.top();
		ret.pop();
		if (tmp->left)
		{
			ret.push(tmp->left);
		}
		if (tmp->right)
		{
			ret.push(tmp->right);
		}
	}
	reverse(data.begin(), data.end());
	for (auto e : data)
	{
		cout << e << " ";
	}
}

    后序遍历2:

  前面的后序遍历是使用先序遍历进行复现的,那么有没有方法可以直接实现后序遍历呢?当然有了。这个方法和我们的中序遍历有些许类似之处,但是我们需要多设置几个变量进行数据的保存。

  首先我们需要设置一个栈用于保存节点的数据。之后我们还需要设置一个cur指针,用于作为节点移动的标志,我们还需要设置一个prev指针,用于记录我们是否已经遍历过右边子树的节点,防止重复遍历。因为我们的遍历顺序是:左右根,根是最后访问的,那么我们就需要遍历完右子树之后再访问根节点。但是我们有需要和从根节点进入右节点当中的情况进行区分,所以我们需要设置一个prev指针进行记录。其中保存的是刚刚执行过操作的右子树的节点的指针。

  我们需要进行的操作大致如下:首先我们需要找到左子树的根节点,当我们一直进行访问并将节点的指针加入到栈当中,直到遇到空节点之后。这个时候cur指向nullptr,我们需要让cur拿到栈顶的元素,也就是最近的左子树的节点,这个时候我们需要对该节点的右子树进行判断。因为我们想要先访问右子树的节点。所以如果右子树的节点不为空的时候我们需要将cur的值修改成为右子树的指针。如果为空就删除拿到的top节点。并打印输出数据。因为这个时候我们的右子树为空,也就是说应该遍历根节点了。在打印输出数据之后,就代表以该节点为根节点的树已经全部遍历完成了。那么我们就需要将prev设置成为该节点的指针,防止在执行下一次操作的时候,重复进入该子树当中造成死循环。过程如图所示:

  根据上述步骤我们可以编写出如下的代码:

void posOrder2(BTNode root)
{
	stack<BTNode> ret;
	BTNode prev = nullptr;
	BTNode cur = root;
	while (cur|| !ret.empty())
	{
		while (cur)
		{
			ret.push(cur);
			cur = cur->left;
		}
		BTNode top = ret.top();
		if (top->right == nullptr || top->right == prev)
		{
			ret.pop();
			cout << top->_val << " ";
			prev = top;
		}
		else
		{
			cur = top->right;
		}
	}
}

  测试一遍我们使用栈进行树的遍历的输出结果:

  对比我们之前使用递归的形式编写的代码,结果一切正常。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1716773.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode 63.不同路径Ⅱ

思路&#xff1a; 在有障碍物的地方增加一个判断即可 class Solution { public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int dp[105][105];int mobstacleGrid.size();int nobstacleGrid[0].size();for(int i0;i<m;i){for(int j0…

拓数派与浙江平数举行「政务数据服务产品合作开发」签约仪式

3月14日&#xff0c;杭州拓数派科技发展有限公司&#xff08;以下简称“拓数派”&#xff09;与浙江平数科技有限公司&#xff08;以下简称“浙江平数”&#xff09;举行了关于政务数据服务产品合作开发的签约仪式。在嘉兴平湖市政务服务管理办公室党委副书记、主任&#xff0c…

移动硬盘未格式化数据恢复及预防策略

随着数字化时代的到来&#xff0c;移动硬盘作为数据存储的重要载体&#xff0c;被广泛应用于个人和企业中。然而&#xff0c;当移动硬盘遭遇“未格式化”的困境时&#xff0c;其中的数据便岌岌可危。本文将深入探讨移动硬盘未格式化的现象、原因、数据恢复方案以及预防措施&…

linux开放某一个端口具体步骤

场景&#xff1a;当服务器防火墙不能直接关闭&#xff0c;但是客户端必须要访问服务器某一个端口时。 处理&#xff1a;对服务器端进行处理&#xff0c;只将该端口开放出来让客户端访问 本地使用vm安装了一个centos服务器&#xff0c;ip地址是 192.168.200.130。在这里充当服…

Python-3.12.0文档解读-内置函数bytes()详细说明+记忆策略+常用场景+巧妙用法+综合技巧

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 详细说明 概述 构造函数 参数说明 示例 字节串字面值 操作与方法 相关类型 记…

富凡行是什么软件,来具体聊一聊它的详情,感兴趣的不要错过了

目前做网络项目的人很多&#xff0c;也就衍生出了很多的软件、项目、平台。接触过了很多的产品&#xff0c;感触颇深&#xff0c;确实市面上的东西差别都很大&#xff0c;有好的&#xff0c;有不好的。 我也是喜欢在网上做点副业&#xff0c;自己捣鼓一下&#xff0c;毕竟互联网…

基于51单片机的温湿度控制系统

一.硬件方案 本设计采用51单片机每2秒钟从DHT11温湿度传感器中读入温度和湿度&#xff0c;在液晶屏上即时显示。液晶屏上同时显示温湿度上限值&#xff0c;该上限值保存外外部EEPROM存储器中&#xff0c;掉电不失&#xff0c;并且可以通过四只按键上调或下调。当温度或湿度值超…

Linux驱动开发笔记(二) 基于字符设备驱动的GPIO操作

文章目录 前言一、设备驱动的作用与本质1. 驱动的作用2. 有无操作系统的区别 二、内存管理单元MMU三、相关函数1. ioremap( )2. iounmap( )3. class_create( )4. class_destroy( ) 四、GPIO的基本知识1. GPIO的寄存器进行读写操作流程2. 引脚复用2. 定义GPIO寄存器物理地址 五、…

小红书图文笔记怎么做?纯干货!

小红书图文笔记的制作是一门艺术&#xff0c;它需要结合精美的图片和有价值的内容&#xff0c;以吸引和留住用户的注意力。伯乐网络传媒给大家分享制作小红书图文笔记的干货指南&#xff0c;包括准备、制作、发布和优化的各个环节。 一、准备阶段 确定目标受众&#xff1a;找到…

kubernetes-PV与PVC、存储卷

一、PV和PVC详解 当前&#xff0c;存储的方式和种类有很多&#xff0c;并且各种存储的参数也需要非常专业的技术人员才能够了解。在Kubernetes集群中&#xff0c;放了方便我们的使用和管理&#xff0c;Kubernetes提出了PV和PVC的概念&#xff0c;这样Kubernetes集群的管理人员就…

npm镜像源管理、nvm安装多版本node异常处理

查看当前使用的镜像源 npm config get registry --locationglobal 设置使用官方源 npm config set registry https://registry.npmjs.org/ --locationglobal 设置淘宝镜像源 npm config set registry https://registry.npm.taobao.org/ --locationglobal 需要更改淘宝镜像源地址…

uniapp登录成功后跳回原有页面+无感刷新token

uniapp登录成功后跳回原有页面 引言 在C端的页面场景中&#xff0c;我们经常会有几种情况到登录页&#xff1a; 区分需要登录和不用登录的页面&#xff0c;点击需要登录才能查看的页面 已经登录但是超时&#xff0c;用户凭证失效等原因 以上情况可以细分为两种&#xff0c;一…

自动化测试实践:揭秘WebSocket在接口测试中的应用

如何写接口自动化&#xff1f;这个问题&#xff0c;但凡涉足过自动化测试的人员都能娓娓道来。Requests、urlib、jmeter、curl等等&#xff0c;不在话下。那么&#xff0c;如何获取接口的url、参数、响应等信息呢&#xff1f;&#xff01;答案就更是随口而出&#xff1a;看接口…

必看项目|多维度揭示心力衰竭患者生存关键因素(生存分析、统计检验、随机森林)

1.项目背景 心力衰竭是一种严重的公共卫生问题,影响着全球数百万人的生活质量和寿命,心力衰竭的病因复杂多样,既有个体生理因素的影响,也受到环境和社会因素的制约,个体的生活方式、饮食结构和医疗状况在很大程度上决定了其心力衰竭的风险。在现代社会,随着生活水平的提…

利用英特尔 Gaudi 2 和至强 CPU 构建经济高效的企业级 RAG 应用

检索增强生成 (Retrieval Augmented Generation&#xff0c;RAG) 可将存储在外部数据库中的新鲜领域知识纳入大语言模型以增强其文本生成能力。其提供了一种将公司数据与训练期间语言模型学到的知识分开的方式&#xff0c;有助于我们在性能、准确性及安全隐私之间进行有效折衷。…

计算机网络-Traffic-Filter流量过滤策略

一、概述 为提高网络安全性&#xff0c;管理人员需要控制进入网络的流量&#xff0c;将不信任的报文丢弃在网络边界。所谓的不信任报文是指对用户来说存在安全隐患或者不愿意接收的报文。同时保证数据访问安全性&#xff0c;企业网络中经常会要求一些部门之间不能相互访问。 背…

金融行业专题|超融合对国密卡和国产加密技术的支持能力如何?

目前&#xff0c;不少金融机构都使用国密卡&#xff08;满足国密算法要求的加密卡&#xff09;和国产密码解决方案保障金融信息安全。而在传统虚拟化架构下&#xff0c;单块加密卡通常只能服务一个系统&#xff0c;经常会出现资源利用率低、加密处理性能不足等问题&#xff0c;…

神经网络与深度学习——第14章 深度强化学习

本文讨论的内容参考自《神经网络与深度学习》https://nndl.github.io/ 第14章 深度强化学习 深度强化学习 强化学习&#xff08;Reinforcement Learning&#xff0c;RL&#xff09;&#xff0c;也叫增强学习&#xff0c;是指一类从与环境交互中不断学习的问题以及解决这类问题…

最简单的安卓模拟器抓包?

安装模拟器抓包似乎是有个绕不开的话题&#xff0c;但是现在普遍的安卓模拟器抓包会遇到以下问题&#xff1a; 1.证书配置繁琐 2.模拟器不兼容软件 3.系统设置繁琐。 前几天写过一次微信小程序如何抓包&#xff0c;现在来讲一下模拟器怎么抓包吧。首先使用的工具还是TangGo测…

开源集运wms系统

集运WMS系统是一种专为集运业务设计的仓库管理系统&#xff0c;它能够高效地处理来自多个来源的货物&#xff0c;优化存储和发货流程。 经过长时间的开发和测试&#xff0c;推出了我的集运WMS系统。它不仅具备传统WMS系统的所有功能&#xff0c;还针对集运业务的特点进行了特别…