二叉树练习

devtools/2024/12/22 16:28: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);不能使用断言,因为我们要将数据存放在节点的下一个,地址是NULLif (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—>leftroot->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.ppmy.cn/devtools/99060.html

相关文章

【微服务】Nacos配置中心和客户端数据同步模式

一、Nacos概述 Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它提供了一组简单易用的特性集&#xff0c;帮助用户快速实现动态服务发现、服务配置、服务元数据及流量管理。 二、数据同步模式 1. 实时同步 Push模式&#xff1a;在服务端的配置信…

leetcode_59. 螺旋矩阵 II

59. 螺旋矩阵 II 题目描述&#xff1a;给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a;​ 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&…

[Matsim]Matsim学习笔记-drt场景中车辆调度的学习

学习需求 在用matsim实现交通流模拟drt场景时&#xff0c;遇到这样一个问题&#xff1a;车辆接送完乘客后&#xff0c;在没有新的订单之前&#xff0c;车辆一直停在最后一个停靠点上&#xff0c;这样车辆的利用率会较低&#xff0c;想实现一个送完最后一个乘客后&#xff0c;车…

【最经典的79个】软件测试面试题(内含答案)

001.软件的生命周期(prdctrm) 计划阶段(planning)-〉需求分析(requirement)-〉设计阶段(design)-〉编码(coding)->测试(testing)->运行与维护(running maintrnacne) 测试用例 用例编号 测试项目 测试标题 重要级别 预置条件 输入数据 执行步骤 预期结果 0002.问&…

VBA技术资料MF185:图片导入Word添加不同格式说明文字

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

探索TensorFlow:深度学习的未来

标题&#xff1a;探索TensorFlow&#xff1a;深度学习的未来 在当今快速发展的人工智能领域&#xff0c;TensorFlow无疑是最耀眼的明珠之一。TensorFlow是由Google Brain团队开发的一个开源机器学习框架&#xff0c;它以其强大的灵活性、易用性和高效的性能&#xff0c;迅速成…

【速览】计算机网络(更新中)

目录 一、背景二、优缺点三、适用场景四、核心组成分层结构TCP/UDP区别TCP三次握手、四次挥手 HTTP/HTTPS区别无状态长连接、短连接 状态码Cookie和SeesionURI和URL 五、底层原理六、对比参考 一、背景 这个技术出现的背景、初衷和要达到什么样的目标或是要解决什么样的问题。这…

加速指南:如何使用Kimi提升论文写作效率?

在学术研究领域&#xff0c;撰写论文是一项基础且关键的任务&#xff0c;它要求作者不仅要有扎实的专业知识&#xff0c;还要具备高效的信息处理能力和清晰的表达技巧。学术写作是一个复杂的过程&#xff0c;涉及多个阶段&#xff1a;从选题、资料搜集、论文结构设计&#xff0…