二叉树练习

news2024/11/25 7:04:14

1.认识树

树的根节点及其子树,都是相对的概念。在任何一棵树中都有一个根节点,而这棵树本身又可以是别的树的子树。树的基本概念有:

A)双亲和孩子:一个节点的后继节点被称为该节点的孩子,该节点称为这些孩子的双亲。

B)结点的度:一个节点孩子的个数。

C)兄弟:拥有双亲的节点互为兄弟节点;

D)节点的层次:人为规定根节点的层次为1或0(具体看说明),他的后代节点得层依次加一。

E)树的高度:树中结点层次的最大值。

F)终端节点:树最末端的叶子节点。

2.二叉树

二叉树:一个父节点最多只有两个子节点,必须是有序数。在遍历二叉树中一般将大于父节点的子节点放在父节点的右边,小的放左边。只有一个节点也要严格区分左右。

满二叉树:如果有K层,那节点个数数等于2^K-1。

2.1二叉树的遍历方式:

A):前序:先遍历根节点(父母节点),再遍历左孩子,最后遍历右孩子。

B)中序:先遍历左孩子,再遍历根节点,最后遍历右孩子。

C)后序:先遍历左孩子,再遍历右孩子,最后遍历根节点。

D)按层遍历:按树的每一层来遍历兄弟节点(使用队列来实现)

2.2二叉树性质练习

前序:50 40 30 49 80 70 75

中序:30 40 49 50 70 75 80

后序:30 49 40 75 70 80 50

这就是结果:根据上述的遍历方式的描述感受一下,拿后序来讲:先左再右最后父节点。50有两个子节点,我们就先看左子节点,左子节点是30和49的父节点,所以根据相对父子关系,第一个就是30,第二个49,最后父节点40,50的左就完了下来是50的右由于80有一个子节点,70也有子节点,由于70的左子节点没有,所首先是75,70,80,50。最终答案就是30 49 40 75 70 80 50。

上面是根据树写遍历结果,下面我们练习一下根据遍历结果画出树的结构

若某二叉树前序遍历的结果为:abdgcehf,中序遍历结果为:dgbaechf,则后序遍历结果为?

这种我们就要用前两个的条件画出树的结构。

第一步:根据前序我们知道第一层节点为a,又因为中序遍历a的左右都有数据因此

前序遍历a下来是b,中序遍历b的右边是a因此b没有右子节点。因此

 根据前序b下来是d,根据中序d的左边没有,因此d只有右子节点,因此

根据前节点,d节点下来是g,g的左右是d和b我们已经确定了,因此g没有子节点。

 用同样的方法,a的右边就是c,c有e,h两个子节点,e在左,h在右

根据前序,f是h的子节点,但是在左还是在右无法确定,我们再看中序 ,中序h无左子节点,所以f在h的右节点

最终根据后序遍历:g d b e f h c a

3.二叉搜索树(BST)程序的实现

我们要实现插入,删除等操作,我们将使用链式结构实现搜索二叉树。

3.1节点设计

我们设计节点是需要两个指针,一个数据,因此设计如下。

//节点设计
typedef struct tree 
{

	Type Data;
	struct tree* Left, * right;
}tree,* p_tree;

3.2节点初始化

树的叶节点左右指针一定是指向NULL的,我们在添加节点那么这个节点一定是树叶子节点。因此设计初始化如下。

//树节点初始化
p_tree TreeNodeInit(Type Data)
{
    p_tree NewAddr = (p_tree)calloc(1, sizeof(tree));
    assert(NewAddr);
    NewAddr->Data = Data;
    NewAddr->Left = NULL;
    NewAddr->right = NULL;
}

从键盘获取用户想要存储的数据

//交互让用户输入自己想要输入的数据
Type GetUsrInput(char* SAddr)
{
	printf("%s\n", SAddr);
	Type num = 0;
	Type ret_val = 0;

	while (ret_val != 1)
	{
		ret_val = scanf("%d", &num);
		//如果数据获取失败
		if (ret_val != 1)
		{
			while (getchar() != '\n');
			continue;
		}
	}
	return num;
}

3.3添加节点到树

这里我们采用递归进行操作,因为我们也不知道要找多少次才能找到树的叶子节点,并且这些存储过程的方式是重复的。

我们是大于父节点的数据放在右子节点,否则放在左子节点。进来我们要看传入的根节点是否为空,如果是空那么我们就找到了要存储新节点的位置,将新节点的地址返回,让当前叶子节点的左或右指向我们的新节点就好了。

这样讲可能还是不太明白。举个例子

首先我们创建一个节点,并且初始化存储值为50

进来我们要看传入的根节点是否为空,如果是空那么我们就找到了要存储新节点的位置,但是传进来这个根节点不为空,我们再次调用经过比较判断再一次进入递归,p_tree AddNodeToTree(p_tree root, p_tree New),这一次p_tree root传入的是NULL然后我们就直接返回了要存储叶子节点的地址,让其父节点指向它。

//添加数据到树
//大于根节点的数据放在右边,小于根节点的数据放在左边
//采用递归操作
p_tree AddNodeToTree(p_tree root, p_tree New)
{
	//assert(root);不能使用断言,因为我们要将数据存放在节点的下一个,地址是NULL
	if (root == NULL)
	{
		return New;
	}
	if (New->Data > root->Data)
	{
		root->right = AddNodeToTree(root->right, New);
	}
	else
	{
		root->Left = AddNodeToTree(root->Left, New);
	}
	//递归到底层出来返回根节点
	return root;
}

3.4遍历二叉搜索树

同样的我们采用递归的方式:

比如采用中序遍历:我们要先遍历左再遍历父节点最后遍历右。先找到最左如果为空则输出还是那个一个节点的Data,在打印右侧的树的左节点,代码如下。大家可以画个图理解一下。

//中序遍历
void treeEachMedium(p_tree root)
{
	if (root == NULL)
		return;
	treeEachMedium(root->Left);
	printf("%d\t", root->Data);
	treeEachMedium(root->right);
	return;
}

3.5删除二叉树的节点

删除二叉树的节点思路,假如有这么一个树:

我们知道二叉搜索树根节点左边一定全是小于根节点的数,右边一定是大于根节点的数。我们要删除节点首先要找到要删除的节点,然后再找一个合适的节点替换掉我们删除的节点。那这个节点怎么找呢?

假如我们删除50那我们是用40替掉50还是80替掉50,如果用40,那左边47大于40不符合二叉搜索树,用30也不行。它既然替换掉左边的数都是小于根节点的那我们可以找左边最大的数来替换,找右边最小的数来替换。好了我们现在知道找谁替换了,思考一下我们删除的节点必须要替换吗?

并不是,如果删除的是叶子节点我们就不需要替换!注意替换只是替换Data,删除节点的指向不需要改变,替换节点地址要变为NULL。用47替换46,那47的节点地址要变为空并返回,46的左右指向不变,递归退出返回赋值即可。

例如:我们要删除40这个节点我们来一一讲解!

首先我们要判断传进来节点是否为空,如果为空那么就没有要找的节点

//删除指定树的节点
p_tree DelTreeNode(p_tree root, Type Data)
{
	//判断节点是否为空,如果为空那么树没有该节点
	if (root == NULL)
	{
		printf("树中没有该数据");
		return root;
	}
}

如果不为空我们,我们根据找的数据与节点值的判断王座还是往右找->递归。假设我们要找46

找到之后,我们就有46节点的地址root,可以执行一个循环操作找到替换46的节点

p_tree DelTreeNode(p_tree root, Type Data)
{
	//判断节点是否为空,如果为空那么树没有该节点
	if (root == NULL)
	{
		printf("树中没有该数据");
		return root;
	}
	//往节点左侧找
	if (Data < root->Data)
	{
		root->Left = DelTreeNode(root->Left, Data);
	}
	//往节点右侧找
	else if (Data > root->Data)
	{
		root->right = DelTreeNode(root->right, Data);
	}
	//找到数据
	else
	{
		//如果该节点左边有数据,那就找左边最大的数据
		//否则找右边最小的数据
		//两边都没有直接删
		free(root);
	}

}

 下面这个代码是否可行,主要是找到替换的数据然后将替换数据空间释放掉,然后让空间地址等于空可以吗?

//删除指定树的节点
p_tree DelTreeNode(p_tree root, Type Data)
{
	//判断节点是否为空,如果为空那么树没有该节点
	if (root == NULL)
	{
		printf("树中没有该数据");
		return root;
	}
	p_tree Temp = NULL;
	//往节点左侧找
	if (Data < root->Data)
	{
		root->Left = DelTreeNode(root->Left, Data);
	}
	//往节点右侧找
	else if (Data > root->Data)
	{
		root->right = DelTreeNode(root->right, Data);
	}
	//找到数据
	else
	{
	
		//如果该节点左边有数据,那就找左边最大的数据
		if (root -> Left != NULL)
		{
			for (Temp = root->Left; Temp != NULL; Temp = Temp->right);//这样就找到了左边最大的节点地址
			//此时:root是要删除数据的节点地址,Temp是替换数据的地址
			root->Data = Temp->Data;
		}
		//否则找右边最小的数据
		if (root -> right != NULL)
		{
			for (Temp = root->right; Temp != NULL; Temp = Temp->Left);//这样就找到了右边最小的节点地址,这里有个错误Temp != NULL,结束时Temp ==NULL,因此要Temp改为Temp—>left
			root->Data = Temp->Data;
		}
		//要删的数据就是叶子节点
		else
		{
			Temp = root;
		}
		//两边都没有直接删
		free(Temp);
		Temp = NULL;
	}
	return Temp;

}

答案是不行的,虽然替换的点为空了,但是并没有将这个空地址赋给父节点,导致父节点仍然指向释放的那片空间。最终可以这样写:

//删除指定树的节点
p_tree DelTreeNode(p_tree root, Type Data)
{
	//判断节点是否为空,如果为空那么树没有该节点
	if (root == NULL)
	{
		//printf("树中没有该数据");
		return root;
	}
	p_tree Temp = NULL;
	//往节点左侧找
	if (Data < root->Data)
	{
		root->Left = DelTreeNode(root->Left, Data);
	}
	//往节点右侧找
	else if (Data > root->Data)
	{
		root->right = DelTreeNode(root->right, Data);
	}
	//找到数据
	else
	{
		if (root->Left == NULL && root->right == NULL)
		{
			free(root);
			root = NULL;
			return root;
		}
		//如果该节点左边有数据,那就找左边最大的数据
		else if (root -> Left != NULL)
		{
			for (Temp = root->Left; Temp->right != NULL; Temp = Temp->right);//这样就找到了左边最大的节点地址
			//此时:root是要删除数据的节点地址,Temp是替换数据的地址
			root->Data = Temp->Data;
			root->Left = DelTreeNode(root->Left, Temp->Data);
		}
		//否则找右边最小的数据
		else
		{
			for (Temp = root->right; Temp->Left != NULL; Temp = Temp->Left);//这样就找到了右边最小的节点地址
			root->Data = Temp->Data;
			root->right = DelTreeNode(root->right, Temp->Data);
		}
	}
	return root;

}

 3.6二叉搜索树销毁

我们只能采用后序遍历的方式来销毁二叉树,前序遍历就会找不大左右子节点,....。

p_tree DestroyedTree(p_tree root)
{
	if (root == NULL)
		return NULL;
	DestroyedTree(root->Left);
	DestroyedTree(root->right);
	printf("free:%d\n", root->Data);
	free(root);
	root = NULL;
	return root;
}

3.7按层遍历

 这是按层遍历的流程我们来感受一下:

1、首先让50入队,遍历队头的值,判断50的左右是否为空,如果都为空50出队,否则先让不为空的左节点入队,再让不为空的右节点入队,再让50出队。

经过第一步队列里只剩40,80,并且50已经输出。

2、在判断40的左右是否为空,如果都为空40出队,否则先让不为空的左节点入队,再让不为空的右节点入队,再让40出队。

3、在判断80的左右是否为空,如果都为空80出队,否则先让不为空的左节点入队,再让不为空的右节点入队,再让40出队。

经过2,3步,就已经遍历完成第二层,队列里只剩下30,46,70

直到队列中没有数据,遍历结束。 

这样就实现了按层遍历!!

//1.创建一个空队列
P_temp CreateTeamNode(p_tree Data)
{
	P_temp NewHead = (P_temp)calloc(1, sizeof(team));
	if (NewHead == NULL)//如果根节点不为空则入队
	{
		printf("创建队列节点失败\n");
		return NULL;
	}
	NewHead->teamNext = NewHead;
	return NewHead;
}

//2.将根节点入队
void AddDataInTeam(p_tree root, P_temp TempHead)
{
	if (root == NULL)
	{
		printf("树为空");
		return;
	}
	P_temp tail = NULL;
	P_temp TempNewHead = CreateTeamNode(root);
	//找到尾节点
	for (tail = TempHead; tail->teamNext != TempHead; tail = tail->teamNext);
	tail->teamNext = TempNewHead;
	TempNewHead->teamNext = TempHead;
	return;
}


//删除队头元素
void delTeamHead(P_temp TempHead)
{
	P_temp DelTeamData;
	P_temp NextTeamData;
	DelTeamData = TempHead->teamNext;
	NextTeamData = TempHead->teamNext->teamNext;
	free(DelTeamData);
	TempHead->teamNext = NextTeamData;
}

//出队函数
bool ExitTeam(P_temp TempHead)
{
	//3.判断队列是否为空如果为空则遍历结束,否则出队头元素
	if (TempHead->teamNext == TempHead)
	{
		printf("队列为空遍历结束!!\n");
		return false;
	}
	P_temp TeamNext = TempHead->teamNext;
//4.访问队头元素
	printf("%d\n", TeamNext->Data->Data);

//5.如果队头元素,左不为空,则左节点入队
	if (TeamNext->Data->Left != NULL)
	{
		AddDataInTeam(TeamNext->Data->Left, TempHead);
	}
//6.如果队头元素,右不为空,则右节点入队
	if (TeamNext->Data->right != NULL)
	{
		AddDataInTeam(TeamNext->Data->right, TempHead);
	}
	//删除队头
	delTeamHead(TempHead);
	return true;
}

//按层遍历二叉搜索树
void EachTreeTier(p_tree root)
{
	P_temp Data;
//1.创建一个空队列
	P_temp TempHead = CreateTeamNode(NULL);
//2.将根节点入队
	AddDataInTeam(root, TempHead);


//7.开始第三步重复操作。
	while (ExitTeam(TempHead));
	
	
}

 

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

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

相关文章

面试车载测试岗位,我们应该如何准备呢?

在进行车载测试方面的简历撰写以及面试时&#xff0c;我们需要注意的几点如下&#xff1a; 01、简历方面 1.没有相关项目怎么办? 要投递和面试的岗位所要参与的项目和做过的项目不可能是完全一样的。招聘企业更关注工作思路以及解决问题的思路。 不同的公司就算是做一样的项…

无人机:航拍书籍推荐

写在前面 学习航拍&#xff0c;整理一些书籍分享理解不足小伙伴帮忙指正 &#x1f603;,生活加油 99%的焦虑都来自于虚度时间和没有好好做事&#xff0c;所以唯一的解决办法就是行动起来&#xff0c;认真做完事情&#xff0c;战胜焦虑&#xff0c;战胜那些心里空荡荡的时刻&…

JavaWeb基础 -- Servlet

JavaWeb基础 – Servlet 1.Servlet简介 1.1 Servlet是什么 Servlet本身是用Java编写的&#xff0c;运行在Web服务器上的应用程序&#xff0c;并作为Web浏览器和其他HTTP客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。Servlet可以收集来自网页表单输入的数据…

iOS 修改 tabbar 图标大小 01

0x00 transform 在点击 tabbar 时&#xff0c;通过修改图片的 transform 属性&#xff0c;来修改图片大小。 遍历 self.tabBar.subviews 来查找 图片。 imageView.transform CGAffineTransformScale(imageView.transform, 4, 4); 你会发现&#xff0c;根本改不动&#xff01…

CSS知识点详解:div盒子模型

盒子模型&#xff1a; 边框&#xff1a; border-color&#xff1a;边框颜色 border-width&#xff1a;边框粗细 1.thin 2.medium 3.thick 4.像素值 border-width:5px ; border-width:20px 2px; border-width:5px 1px 6px; border-width:1px 3px 5px 2px; 这个简写属性…

豆瓣评分9.0!Python3网络爬虫开发实战,堪称教学典范!

今天我们所处的时代是信息化时代&#xff0c;是数据驱动的人工智能时代。在人工智能、物联网时代&#xff0c;万物互联和物理世界的全面数字化使得人工智能可以基于这些数据产生优质的决策&#xff0c;从而对人类的生产生活产生巨大价值。 在这个以数据驱动为特征的时代&#…

Python导出所有已安装包及其版本信息

目录 导出导入 如果使用了虚拟环境&#xff0c;则先激活当前项目虚拟环境 venv\Scripts\activate导出 在当前目录下生成一个requirement.txt文件&#xff0c;记录当前环境的所有pyhton依赖包及其版本信息。 pip freeze > requirement.txt导入 pip install -r requiremen…

C语言手撕实战代码_循环单链表和循环双链表

C语言手撕实战代码_循环单链表和循环双链表 循环单链表习题1.建立带头结点的循环链表2.设计一个算法&#xff0c;将一个带有头结点的循环单链表中所有结点的链接方向逆转3.设计一个算法,将一个循环单链表左移k个结点4.设计一个算法将循环单链表中的结点p的直接前驱删除5.设计算…

如何使用 Higress 快速构建 AI 应用?

随着 AI 时代到来&#xff0c;基于大模型的应用对网关提出了新的要求&#xff0c;例如在不同 LLM 提供商之间进行负载均衡、构建 AI 应用的可观测能力、基于 token 的限流保护与配额管理、AI 应用内容安全等等。Higress 基于企业内外的丰富场景沉淀了众多面向AI的功能&#xff…

pip3 : 无法将“pip3”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。

1.找到python安装目录底下的Scripts&#xff0c;复制该路径&#xff1a;你安装python的目录\Scripts 我本地的&#xff1a;D:\devSoftware\python-all\Python39\Scripts 2.将该路径配置到环境变量Path里面 我的电脑 - 右键属性 - 系统 - 环境变量 - 找到path点击编辑 - 新建&a…

家用超声波清洗机哪个品牌好用?实测解析四大高口碑超声波清洗机机型

提到超声波清洗机&#xff0c;很多人可能首先想到的是眼镜店或首饰店里的商用清洗设备。它们虽然功能强大&#xff0c;但价格较高且体积较大&#xff0c;并不适合家用。不过&#xff0c;现在有了一个更方便的选择&#xff1a;小型超声波清洗机。它们体积小巧&#xff0c;价格也…

9.cmake(string)

目录 1. 基本用法 2. string对于json的操作 3.代码段 1. 基本用法 以下通过截取字符串"begin test cmake string end "中的子串"test cmake string"来串联string中的部分用法&#xff0c;其中包括了FIND&#xff0c;LENGTH&#xff0c;SUBSTRING&#x…

linux搭建ceph集群

linux三节点搭建ceph集群 主机IP主机名称172.26.50.75node1172.26.50.112node2172.26.50.228node3 ceph-mon&#xff0c;ceph-mgr&#xff0c;ceph-mds都搭建在node1上&#xff0c;node2和node3上搭建ceph-osd&#xff0c;每个机器1个osd Ceph是一个分布式的存储系统&#x…

选择排序(直接选择排序和堆排序)

一、直接选择排序 1.基本思想 每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;直到全部待排序的数据元素排完。 2.动图展示 3.思路讲解 ①在元素集合array[i]—array[n-1]中选择关键码最大&…

个人博客系统-自动化测试

1、项目背景 1.1技术背景 1&#xff09;个人博客系统主要是通过前端&#xff08;HTMLcssjs&#xff09;后端&#xff08;SpringBoot&#xff09;实现的一个博客的基本功能。前端通过jQuery的方式向后端请求数据。后端通过MyBatis从数据库中查询数据响应给前端。 2&#xff0…

天通报警呼叫柱助力宜宾——破汛期河心洲岛通信困境,守护人民群众生命安全

随着主汛期的到来&#xff0c;我国多地遭遇频繁降雨&#xff0c;强降雨或连绵不断的降雨&#xff0c;极易引发山洪、滑坡、泥石流等次生灾害。8月18日23时至20日10时&#xff0c;辽宁省西部地区出现暴雨到大暴雨&#xff0c;葫芦岛市部分乡镇出现特大暴雨。受到强降雨影响&…

Qt-QWidget的windowIcon属性(14)

目录 描述 相关API 使用 并不需要在堆上创建 不要带中文路径 运行观察 不要使用绝对路径 描述 这个其实就是你打开窗口的左上角那个图标&#xff0c;这个就是用来设置那个的 相关API 使用 创建一个新的项目&#xff0c;如下&#xff0c;添加一个设置图片的代码 并不需…

uboot中 fastboot udp 协议分析

注&#xff1a; 1. 本文所分析的fastboot源码不是android下的源码&#xff0c;而是恩智浦芯片厂商在IMX6UL芯片的uboot源码中自己实现的源码&#xff0c;二者不同&#xff0c;请读者注意区分。一些图片是网上找到的&#xff0c;出处不好注明&#xff0c;请见谅。 2. 分析fastbo…

VM——轮廓/快速匹配中的多模版匹配

1、轮廓或者快速匹配中支持建立多个模型&#xff0c;按照从上而下的顺序进行匹配&#xff0c;匹配上了即停止后续模版的匹配。 2、如果要多个模版都参与匹配&#xff0c;则需要打开“全部搜索模式”。 3、延拓阈值 “延拓阈值”&#xff0c;看参数名字不知所云&#xff0c;文档…

CR-NeRF 代码eval.py解析

这段代码是一个用于CR-NeRF&#xff08;Neural Radiance Fields&#xff09;模型的推理脚本。它主要用于生成和保存渲染的图像&#xff0c;并计算图像质量的评价指标&#xff08;如PSNR和SSIM&#xff09;。以下是对这段代码的详细解析&#xff1a; &#xff08;1&#xff09;…