二叉树的非递归遍历

news2024/11/17 13:29:20

目录

前言:

一:前序遍历

二:中序遍历

三:后序遍历

四:层序遍历


前言:

二叉树的非递归遍历需要借助栈和队列以及二叉树的一些基础接口,这些在之前的文章中有讲过,这里就不赘述,不清楚的家人们可以点链接,这里直接给出所有需要的接口。

栈和队列:https://blog.csdn.net/2301_76269963/article/details/129823215?spm=1001.2014.3001.5501

二叉树基础接口:https://blog.csdn.net/2301_76269963/article/details/130231257?spm=1001.2014.3001.5501

代码:

//队列
struct BinaryTreeNode;
//重定义,方便更改存储类型
typedef struct BinaryTreeNode* QDataType;
//结点的结构体
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;
//队列的结构体(头用来开辟链接,尾用来查找)
typedef struct Queue
{
	//头
	QNode* head;
	//尾
	QNode* tail;
}Queue;

//初始化
void QueueInit(Queue* pq)
{
	//断言,不能传空的结构体指针
	assert(pq);
	//初始化,把头和尾都指向空
	pq->head = pq->tail = NULL;
}

//入队列
void QueuePush(Queue* pq,QDataType x)
{
	//断言,不能传空的结构体指针
	assert(pq);
	//申请新结点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		printf("malloc error\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	//如果队列为空,单独处理
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		//原尾指向新结点(链接)
		pq->tail->next = newnode;
		//更新尾
		pq->tail = newnode;
	}
}

//队列销毁
void QueueDestroy(Queue* pq)
{
	//断言,不能传空的结构体指针
	assert(pq);
	//先保存下一个,再释放
	QNode* cur = pq->head;
	while (cur)
	{
		//记录
		QNode* next = cur->next;
		//释放
		free(cur);
		//迭代
		cur = next;
	}
	//头尾都置空
	pq->head = pq->tail = NULL;
}


//出队列(删除)
void QueuePop(Queue* pq)
{
	//断言,不能传空的结构体指针
	assert(pq);
	//断言,队列为空不能删除
	assert(!QueueEmpty(pq));
	//保存原头的下一个结点位置
	QNode* newhead = pq->head->next;
	//释放
	free(pq->head);
	//迭代
	pq->head = newhead;
	//如果删除结束了,要把tail指向空(避免野指针)
	if (pq->head == NULL)
		pq->tail = NULL;
}

//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
	//断言,不能传空的结构体指针
	assert(pq);
	/*if (pq->head == NULL)
		return true;
	else
		return false;*/
	//依据判断语句的指直接返回
	return pq->head == NULL;
}

//查找队列的头数据
QDataType QueueFront(Queue* pq)
{
	//断言,不能传空的结构体指针
	assert(pq);
	//断言,队列为空不能查找
	assert(!QueueEmpty(pq));
	return pq->head->data;
}



//栈
struct BinaryTreeNode;
//重定义数据类型,方便更改
typedef struct BinaryTreeNode* STDataType;

typedef struct stack {
	//存储数据
	STDataType* a;
	//栈顶(位置)
	int top;
	//容量
	int capacity;
}ST;
//初始化
void StackInit(ST* ps)
{
	//断言,不能传空指针进来
	assert(ps );
	//一开始指向NULL
	ps->a = NULL;
	//把栈顶和容量都置为空
	ps->top = ps->capacity = 0;
}

//销毁
void StackDestroy(ST* ps)
{
	//断言,不能传空指针进来
	assert(ps );
	//栈顶和容量置为空
	ps->top = ps->capacity = 0;
	//释放空间
	free(ps->a);
	ps->a = NULL;
}

//入栈
void StackPush(ST* ps, STDataType x)
{
	//断言,不能传空指针进来
	assert(ps);
	//先判断是否扩容
	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : (ps->capacity) * 2;
		//扩容
		STDataType* tmp = 
		(STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);
		//扩容失败
		if (tmp == NULL)
		{
			printf("realloc error\n");
			exit(-1);
		}
		//更新
		ps->capacity = newcapacity;
		ps->a = tmp;
	}
	//存储数据
	ps->a[ps->top] = x;
	ps->top++;
}


//出栈(删除)
void StackPop(ST* ps)
{
	//断言,不能传空指针进来
	assert(ps);
	//如果栈为空,不能出栈
	assert(!StackEmpty(ps));
	ps->top--;
}

//取顶部数据
STDataType StackTop(ST* ps)
{
	//断言,不能传空指针进来
	assert(ps);
	//如果栈为空,不能进行访问
	assert(!StackEmpty(ps));
	//返回栈顶数据
	return ps->a[ps->top-1];
}
//判断栈是否为空
bool StackEmpty(ST* ps)
{
	//断言,不能传空指针进来
	assert(ps);
	//依据top来判断
	/*if (ps->top == 0)
		return true;
	return false;*/
	//更简洁的写法,一个判断语句的值要么为true,要么false
	return ps->top == 0;
}
//求树的节点数
int BinaryTreeSize(BTNode* root)
{
	/*if (root == NULL)
	{
		return 0;
	}
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;*/
	//更加简洁的写法
	return root == NULL ? 0 :
		BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

//手动建立一个二叉树
int main()
{
	BTNode* nodeA = BuyNewNode('A');
	BTNode* nodeB = BuyNewNode('B');
	BTNode* nodeC = BuyNewNode('C');
	nodeA->left = nodeB;
	nodeA->right = nodeC;
	BTNode* nodeD = BuyNewNode('D');
	BTNode* nodeE = BuyNewNode('E');
	BTNode* nodeF = BuyNewNode('F');
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;
}




一:前序遍历

思路(配合代码和后面的图解看):

  1. 将根节点入栈,设计一个指针p遍历节点
  2. 只要栈不为空或者p不为空,就进行循环
  3. 节点不为空,先打印节点数据,再入栈,让指针指向节点左孩子
  4. 节点为空,取到栈顶数据(节点父亲),出栈,让指针指向父亲右孩子
  5. p为空并且栈为空,结束

代码:

//非递归前序遍历
void NotRecPreOrder(BTNode* root)
{
	ST s;
	StackInit(&s);
	BTNode* p = root;

	while (!StackEmpty(&s) || p)
	{
		//不为空
		if (p)
		{
			//入栈
			StackPush(&s, p);
			printf("%c ", p->data);
			p = p->left;
		}
		//为空
		else
		{
			//拿到栈顶
			p = StackTop(&s);
			StackPop(&s);
			p = p->right;
		}
	}
	StackDestroy(&s);
}

图解:

一开始p不为空,我们把A打印,入栈,p指向B

p又不为空,我们把B打印,入栈,p指向D

p又不为空,我们把D打印,入栈,p指向空

 

然后就是重点了,p为空, 我们拿到栈顶数据,也就是D(地址),然后出栈,p指向D的右孩子

右孩子也为空,再拿到栈顶(B地址),出栈,p指向B的右孩子,这个时候以B为根的树的左子树就遍历完了,开始遍历B的右子树

B的右子树为空,再拿到栈顶(A地址),出栈,这个时候以A为根的树的左子树就遍历完了,开始遍历右子树,重复这个过程一直到栈空p空,就可以遍历整棵树。

二:中序遍历

思路(配合代码和图解):

  1. 将根节点入栈,设计一个指针p用来遍历节点
  2. 只要栈不为空或者p不为空,就进行循环
  3. 节点不为空,入栈,让指针指向节点左孩子
  4. 节点为空(这个时候父亲节点的左子树一定遍历完了),取到栈顶数据(节点父亲),出栈,打印,让指针指向父亲右孩子
  5. p为空并且栈为空,结束

代码:

//非递归中序遍历
void NotRecInOrder(BTNode* root)
{
	BTNode* p = root;
	ST s;
	StackInit(&s);

	while (p || !StackEmpty(&s))
	{
		//不为空
		if (p)
		{
			StackPush(&s, p);
			p = p->left;
		}
		//为空
		else
		{
			//拿到栈顶
			p = StackTop(&s);
			//出栈
			StackPop(&s);
			printf("%c ", p->data);
			p = p->right;
		}
	}
	StackDestroy(&s);

}

图解:

一开始p不为空,我们把A入栈,p指向B

p又不为空,我们把B入栈,p指向D

p又不为空,我们把D入栈,p指向空

接下来就是关键点了,p为空,这个时候以D为根的树的左子树完成遍历,拿到栈顶(D地址),出栈,然后打印数据,p指向D右孩子。

D右孩子也为空,以B为根的树的左子树完成遍历,拿到栈顶数据(B地址),出栈,打印数据,p指向B右孩子

B的右孩子为空,再拿到栈顶(A地址),出栈,打印数据,这个时候以A为根的树的左子树就遍历完了,开始遍历右子树,重复这个过程一直到栈空p空,就可以遍历整棵树。

三:后序遍历

思路(配合代码和图解):

  1. 将根节点入栈,设计一个指针p用来遍历节点
  2. 只要栈不为空或者p不为空,就进行循环
  3. 节点不为空,入栈,让指针指向节点左孩子
  4. 和前序中序不同,节点为空的时候我们不知道父亲的左右子树是否完成遍历,我们需要设计一个标记数组来判断栈顶节点左右子树是否完成遍历
  5. 第一次进入else内部一定是左子树完成遍历,取到栈顶数据(节点父亲),让指针指向父亲右孩子,标记为1
  6. 如果进入else内部时标记为1,取到栈顶数据(节点父亲),出栈,打印,一直到标记为0的节点,取到这个节点地址,p指向这个节点右孩子
  7. while循环后栈为空,结束循环

代码:

//非递归后序遍历
void NotRecPostOrder1(BTNode* root)
{
	ST s;
	StackInit(&s);
	BTNode* p = root;
	//作为遍历了左子树的依据
	//根据节点数量开辟足够空间
	char* tag = (char*)malloc(sizeof(char) * BinaryTreeSize(root));
	if (tag == NULL)
	{
		printf("malloc error\n");
		exit(-1);
	}
	while (p || !StackEmpty(&s))
	{
		if (p)
		{
			StackPush(&s, p);
			tag[s.top] = '0';//标志结点是否遍历右子树
			p = p->left;
		}
		else
		{
			//如果已经遍历了左子树
			while(tag[s.top] == '1')
			{
				p = StackTop(&s);
				StackPop(&s);
				printf("%c ", p->data);
			}
			if (StackEmpty(&s))
			{
				break;
			}
			//没遍历右子树的情况取到栈顶,遍历右子树
			//遍历了右子树,取得栈顶,遍历上一层的右子树
			p = StackTop(&s);
			p = p->right;
			tag[s.top] = '1';
		}
	}
	StackDestroy(&s);
}

图解:

一开始p指向A,不为空,A(地址)入栈,p指向A左孩子,栈顶标记为0

p指向B,不为空,B入栈,p指向B的左孩子,栈顶标记为0

p指向D,不为空,D入栈,p指向D的左孩子,栈顶标记为0

接下来就是比较关键的地方了,p为空,进入else

但是栈顶(D)的标记为0,代表它的左右子树还没有完成遍历

我们不进入while循环,取到栈顶数据(D地址),然后p指向D的右孩子,将标记修改为1 

 

p为空,进入else

这个时候D的左右子树完成遍历,栈顶的标记为1,进入while循环

取到栈顶数据(D地址),出栈,打印

这个时候tag[top]为0,代表B的左右子树还没有完成遍历,跳出循环

取到栈顶数据(B地址),p指向B的右孩子,标记修改为1

p为空,进入else

这个时候B的左右子树完成遍历,栈顶的标记为1,进入while循环

取到栈顶数据(D地址),出栈,打印

这个时候tag[top]为0,代表A的左右子树还没有完成遍历,跳出循环

取到栈顶数据(A地址),p指向A的右孩子,标记修改为1

p不为空,C(地址)入栈,p指向C左孩子,栈顶标记为0

p指向E,不为空,E入栈,p指向E的左孩子,栈顶标记为0

p为空,进入else

但是栈顶(E)的标记为0,代表它的左右子树还没有完成遍历

我们不进入while循环,取到栈顶数据(E地址),然后p指向E的右孩子,将标记修改为1 

重复上述步骤

 

 跳出while循环后如果栈为空,代表整棵树已经遍历完成,结束循环

四:层序遍历

思路(配合代码和图解):

  1. 将根节点插入队列中,设计一个指针p来遍历节点
  2. 只要队列不为空,就进行循环
  3. 循环中先取到队头数据(地址),然后打印节点数据,最后出队列
  4. 出队列后若该节点有左节点,则将其左节点插入队列中;若该节点有右节点,则将其右节点插入队列中
  5. 队列为空,循环结束(简单来说就是父亲带出孩子)

代码:

//层序遍历
void LevelOrder(BTNode* root)
{
	assert(root);
	Queue Q;
	QueueInit(&Q);
	BTNode* p = root;
	//放入首节点
	QueuePush(&Q, p);

	while (!QueueEmpty(&Q))
	{
		p = QueueFront(&Q);
		printf("%c ", p->data);
		QueuePop(&Q);
		//左入
		if (p->left != NULL)
		{
			QueuePush(&Q, p->left);
		}
		//右入
		if (p->right != NULL)
		{
			QueuePush(&Q, p->right);
		}
	}
	//销毁队列
	QueueDestroy(&Q);
}

图解:

一开始把根节点放入队列中(如果为空树直接报错)

队列不为空,进入循环,取到A地址

出队列,打印数据,A的左右孩子都不为空,入队列

队列不为空,进入循环,取到B地址

出队列,打印数据,B的左孩子不为空,入队列

队列不为空,进入循环,取到C地址

出队列,打印数据,C的左右孩子都不为空,入队列

就这样循环一直到队列为空,层序遍历就完成了。

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

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

相关文章

SSD目标检测

数据集以及锚框的处理 数据集: 图像:(batch_size , channel , height , width) bounding box: (batch_size , m , 5) m: 图像中可能出现的最多边界框的数目 5: 第一个数据为边界框对应的种…

tongweb

13051667606 东方通产品介绍 产品兼容 硬件要求 安装 安装目录结构 启动tongweb 停止tongweb bin下常用命令 企业版管理控制台 文档:产品简介及安装指南 绿色版直接解压安装 tar -zxvf …tar.gz Tongweb的配置文件 在conf的tongweb.xml 修改端口等信息 通过页面…

第四章 Unity工程和相机介绍

在上面的章节中,我们创建了一个“New Unity Project”工程,并保存到了“E:\workspace”工作空间下。那么,我就先看看这个工程的文件结构(E:\workspace\ New Unity Project)。 接下来,我们简单介绍一下这些目…

【老王读SpringMVC-3】根据 url 是如何找到 controller method 的?

前面分析了 request 与 handler method 映射关系的注册,现在再来分析一下 SpringMVC 是如何根据 request 来获取对应的 handler method 的? 可能有人会说,既然已经将 request 与 handler method 映射关系注册保存在了 AbstractHandlerMethodMapping.Ma…

Python 二进制 八进制 十进制 十六进制之间的转换

众所周知:计算机底层是以二进制数来进行存储计算,而计算机进制:数制是用一组固定的符号和统一的规则来表示数值的方法。 开始下面讲述之前首先要声明: 二进制,八进制,十六进制 都可以转换为十进制&#xf…

【DRF配置管理】如何在视图类使用get_objects()

原文作者:我辈李想 版权声明:文章原创,转载时请务必加上原文超链接、作者信息和本声明。 DRF应用和管理 【DRF配置管理】Django使用DRF框架 【DRF配置管理】如何在视图类配置参数(一) 【DRF配置管理】如何在视图类配置参数(二) 【DRF配置管理…

第二届广州·琶洲算法大赛启动,百度飞桨助力广州打造中国算法新高地

‍‍ 生成式人工智能热潮席卷全球,算法创新成为 AI 突破发展的关键,推动实体经济高质量增长。4月25日,第二届广州琶洲算法大赛正式启动,广州市政府主办、百度飞桨等联合承办,广召天下算法英雄,加快“琶洲算…

<网络编程>网络套接字

目录 理解源IP地址和目的IP地址 认识端口号 端口号和进程ID的关系 理解源端口号和目的端口号 初步认识TCP、UDP协议 TCP协议 UDP协议 网络字节序列 socket网络接口 socket常见API sockaddr结构 UDPsocket 编码: 理解源IP地址和目的IP地址 源IP&#xf…

服装店铺装修有哪些窍门?做好这3点,顾客主动上门

现在街边有各种各样的服装店,有的服装店客流不断,有的服装店却很冷清,导致这种现象的原因有很多,比较重要的一点就是你的服装店铺装修没做好。 你的服装店铺装修足够吸引人吗? 什么样的服装店铺装修才能吸引顾客&#…

【技巧】如何修改PDF文件?

PDF文件格式安全、标准化,很多人在工作中几乎离不开。可有些小伙伴想要修改PDF文件内容时,发现无法修改,那是什么情况呢?如何才能修改PDF文件呢?下面小编就来分享一些小技巧。 技巧一:使用PDF编辑器 如果使…

凌恩生物文献分享|一株细菌完成图也能发一区10分+!

期刊:Science of the Total Environment 影响因子:10.753 发表时间:2022 样本类型:Bosea sp. Ads-6菌株 客户单位:中国科学院微生物研究所 一、研究背景 环境中抗生素残留和耐药性的增加引发了许多…

一文详解汽车操作系统现状

摘要: 智能座舱和自动驾驶的发展,特斯拉的突飞猛进,让各大主机厂越来越重视汽车操作系统。但车企现在所做的软件定义汽车,大都是通过软硬件解耦来降低造车成本、丰富新车功能,在操作系统层面大都还停留在市场调研和学…

linux ubantu 16.04 安装fbprophet 和 pystan经验总结

写在前面 之前在window11上,安装了一下午,不是C版本不行,就是这个那个不通过,主要是Pystan运行不起来就很气,fbprophet本身就需要依赖这个包,然后MSVC不支持,裂开。尝试了很多次,碰…

基于Java开发的分布式在线教育系统,支持考试、直播、问答

一、开源项目简介 知道学习平台是一个基于 Java 开发的分布式在线教育系统项目采用前后端分离的企业级微服务架构引入组件化的思想实现高内聚低耦合,项目代码简洁注释丰富上手容易注重代码规范,严格控制包依赖可以帮助个人、企业或机构快速搭建一个在线…

C# 利用TabControl控件制作多窗口切换

TabControl控件切换时触发的事件 选项卡切换触发的是TabControl控件的SelectedIndexChanged事件。 当TabControl控件的任何一个TabPage被点击或选择,即发生SelectedIndexChanged事件事件。 代码如下: private void tabControl1_SelectedIndexChanged(o…

设计模式 --- 结构型模式

一、概述 结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。 由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”…

L2TP Client-initated场景

L2TP Client-initated场景 1. 原理 ![原理](https://img-blog.csdnimg.cn/66ce3169502b4252bca5d9d7a6c0027c.png)1.1 阶段1:创建L2TP隧道 C与LNS通过交互三条消息协商隧道ID、UDP端口(1701)、主机名称、L2TP版本、隧道验证等参数。 1.2 …

使用Spark实现词频统计

文章目录 一,词频统计准备工作(一)版本选择问题(二)安装Scala2.12.15(三)启动集群的HDFS与Spark(四)在HDFS上准备单词文件 二,本地模式运行Spark项目&#xf…

Meta的分割一切模型SAM( Segment Anything )测试

Meta不久前开源发布了一款图像处理模型,即分割一切模型:Segment Anything Model,简称 SAM,号称要从任意一张图片中分割万物,源码地址为: https://github.com/facebookresearch/segment-anything打开后看到…

ssm+java企业公司产品分销商管理系统

一、 二、经营管理: ①分销商每月提交自己进多少货物(从总部进购了多少“鹊巢”的商品给自己负责区的大型商超)——对应的种类一共进多少货物;该种类中具体的产品又进了多少货物具体到(参考三产品管理模块)…