【数据结构】二叉树链式结构的实现及其常见操作

news2024/9/21 18:43:45

目录

1.手搓二叉树

2.二叉树的遍历

2.1前序、中序以及后序遍历

2.2二叉树的层序遍历

3.二叉树的常见操作

3.1求二叉树节点数量

3.2求二叉树叶子节点数量

3.3求二叉树第k层节点个数

3.3求二叉树的深度

3.4二叉树查找值为x的节点

4.二叉树的销毁


1.手搓二叉树

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在我们对二叉树结构掌握还不够深入,为了降低学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

 定义二叉树的节点:

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	BTDataType data;
}BTNode;

根据上图的二叉树结构手写二叉树:

BTNode* BuyNode(BTDataType data)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	assert(node);
	node->data = data;
	node->left = node->right = NULL;
	return node;
}

BTNode* CreatBinaryTree()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);

	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;

	return node1;
}

这样我们就写出了一个简单的二叉树。

2.二叉树的遍历

2.1前序、中序以及后序遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

 按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为 根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

对于二叉树的遍历,代码写起来很简单,但是对于初学者来说,要理解起来就有点难了,这里先给出三种遍历的代码,大家可以先看看:

前序遍历

void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

中序遍历

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

后序遍历

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

看完代码后,是不是觉得这三种遍历都非常的相似呢?我们在编译器上运行一下三种遍历的代码:

int main()
{
	BTNode* root = CreatBinaryTree();
	PreOrder(root);
	printf("\n");
	InOrder(root);
	printf("\n");
	PostOrder(root);
	return 0;
}

运行结果:

 前序遍历的递归展开图:

 中序遍历和后续遍历和这图也差不多。

2.2二叉树的层序遍历

二叉树的层序遍历是一种广度优先搜索(BFS)的方法。它按层级顺序逐层遍历二叉树,即从根节点开始,先遍历第一层节点,然后遍历第二层节点,依次类推,直到遍历完所有层级。

实现层序遍历的一种常见方法是使用队列。具体思路如下:

  1. 创建一个空队列,并将根节点入队。
  2. 循环执行以下步骤,直到队列为空:
  • 出队一个节点,将其值存储到结果列表中。
  • 若该节点有左孩子,则将左孩子入队。
  • 若该节点有右孩子,则将右孩子入队。

这样,当队列为空时,遍历过程就完成了,结果列表中存储着层序遍历的结果。

 下面代码的使用了C++STL中的队列来完成,避免了手写队列的麻烦:

void LevelOrder(BTNode* root)
{
	assert(root);
	queue<BTNode*> a;
	a.push(root);
	while (!a.empty())
	{
		BTNode* front = a.front();
		a.pop();
		printf("%d ", front->data);
		if (front->left)
		{
			a.push(front->left);
		}
		if (front->right)
		{
			a.push(front->right);
		}
	}
}

int main()
{
	BTNode* root = CreatBinaryTree();
	LevelOrder(root);
	return 0;
}

输出结果:

3.二叉树的常见操作

3.1求二叉树节点数量

方法一:

定义一个全局变量count,然后遍历每一个节点,每遍历一个节点,count就自加1

代码:

int count = 0;
void TreeSize1(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	count++;
	TreeSize1(root->left);
	TreeSize1(root->right);
}
int main()
{
	BTNode* root = CreatBinaryTree();
	count = 0;
	TreeSize1(root);
	printf("%d\n", count);
	return 0;
}

方法二:

int TreeSize2(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeSize2(root->left) + TreeSize2(root->right) + 1;
}
int main()
{
	BTNode* root = CreatBinaryTree();
	printf("TreeSize2: %d\n", TreeSize2(root));
	return 0;
}

首先检查根节点是否为空,如果为空,说明这是一个空树,直接返回0。如果根节点不为空,则递归调用自身来计算左子树和右子树的节点数,然后将左子树节点数、右子树节点数以及根节点本身(1个节点)的数量相加,最后返回结果。

通过这种递归的方式,函数可以计算出二叉树中所有节点的总数。

3.2求二叉树叶子节点数量

具体思路:

首先检查根节点是否为空,如果为空,说明这是一个空树,直接返回 0。接着,通过判断根节点的左子树和右子树是否都为空来确定当前节点是否为叶子节点。如果是叶子节点,返回 1。如果不是叶子节点,递归调用自身来计算左子树和右子树的叶子节点数,并将其相加作为结果返回。

int TreeLeafSize(BTNode* root)
{
	if (root == NULL) return 0;
	if (root->left == NULL && root->right == NULL) return 1;
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
int main()
{
	BTNode* root = CreatBinaryTree();
	printf("TreeLeafSize: %d\n", TreeLeafSize(root));
	return 0;
}

3.3求二叉树第k层节点个数

思路:

首先检查根节点是否为空,如果为空,说明这是一个空树,直接返回0。如果k等于1,说明当前层即为目标层,返回1。如果k大于1,则递归调用自身来计算左子树和右子树中第k-1层节点的数量,并将其相加作为结果返回。

通过这种递归的方式,函数可以计算出二叉树中第k层节点的总数。

int TreeKLevel(BTNode* root, int k)
{
	assert(k >= 1);
	if (root == NULL) return 0;
	if (k == 1) return 1;
	return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
int main()
{
	BTNode* root = CreatBinaryTree();
	printf("TreeKLevel: %d\n", TreeKLevel(root, 2));//第2层节点数量
	printf("TreeKLevel: %d\n", TreeKLevel(root, 3));//第3层节点数量
	printf("TreeKLevel: %d\n", TreeKLevel(root, 4));//第4层节点数量
	return 0;
}

函数的递归展开图:

3.3求二叉树的深度

int TreeDepth(BTNode* root)
{
	if (root == NULL) return 0;
	int l = TreeDepth(root->left); //左子树的深度
	int r = TreeDepth(root->right); //右子树的深度
	return (l > r ? l : r) + 1; //返回左右子树深度的较大值加自身的深度1
}
int main()
{
	BTNode* root = CreatBinaryTree();
	printf("TreeDepth: %d\n", TreeDepth(root));
	return 0;
}

首先检查根节点是否为空,如果为空,说明这是一个空树,直接返回深度0。接着,函数通过递归调用自身来计算左子树和右子树的深度,分别将结果存储在变量l和r中。然后,通过比较l和r的大小,选择较大的值,并将其加1(代表当前节点的深度),作为整棵二叉树的深度。

通过这种递归的方式,函数可以计算出二叉树的深度(高度)。

3.4二叉树查找值为x的节点

在二叉树查找值为x的节点时,尽量使用前序遍历,可以提高效率。

BTNode* TreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL) return NULL;
	if (root->data == x) return root;
	BTNode* ret1 = TreeFind(root->left, x);
	if (ret1)
	{
		return ret1;
	}
	BTNode* ret2 = TreeFind(root->right, x);
	if (ret2)
	{
		return ret2;
	}
	return NULL;
}

int main()
{
	BTNode* root = CreatBinaryTree();
	BTNode* ret = TreeFind(root, 3);
	if (ret)
	{
		printf("找到了:%d\n", ret->data);
	}
	else
	{
		printf("找不到\n");
	}
	return 0;
}

首先检查根节点是否为空,如果为空,说明这是一个空树,直接返回NULL。接着,函数检查当前节点的数据是否等于目标值x,如果等于,说明找到了目标节点,返回指向当前节点的指针。如果不等于,递归调用函数分别在左子树和右子树中查找目标值x,如果返回的指针非空,说明在子树中找到了目标节点,直接返回该指针。如果左右子树都没有找到目标节点,则返回NULL。

通过这种递归的方式,函数可以在二叉树中查找特定值的节点,并返回指向该节点的指针。如果找不到目标值,则返回NULL。

4.二叉树的销毁

对于二叉树的销毁,我们不能使用先序遍历,因为如果使用先序遍历,会将二叉树的根节点先销毁掉,这样就无法找到根节点的左子树和右子树了,如果一定要使用先序遍历,那就得先把节点的左子树和右子树先保存下来。但如果使用后序遍历,就可以轻松解决了。

采用后序遍历销毁,按照左孩子,右孩子,根节点的顺序销毁一颗二叉树。

void TreeDestory(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	TreeDestory(root->left);
	TreeDestory(root->right);
	free(root);
}
int main()
{
	BTNode* root = CreatBinaryTree();
	PreOrder(root);
	TreeDestory(root);
	root = NULL;
}

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

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

相关文章

安防监控视频云存储EasyCVR平台H.265转码功能更新:新增分辨率配置

安防视频集中存储EasyCVR视频监控综合管理平台可以根据不同的场景需求&#xff0c;让平台在内网、专网、VPN、广域网、互联网等各种环境下进行音视频的采集、接入与多端分发。在视频能力上&#xff0c;视频云存储平台EasyCVR可实现视频实时直播、云端录像、视频云存储、视频存储…

el-table实现纯前端查询列表(不走后端接口)

2023.8.16今天我学习了如何使用前端进行数据的查询&#xff0c;有时候后端会直接返回全部的数据&#xff0c;这时候我们就需要用前端进行查找数据。 首先elementUI有自带el-table查询的组件&#xff1a; Element - The worlds most popular Vue UI framework 我们发现在这段代…

章节3:防御篇

章节3&#xff1a;防御篇 06 密码暴力破解的防御 暴力破解防御 sleepToken限制尝试次数&#xff0c;锁定账户 二次验证 reCAPTCHA&#xff08;IP验证&#xff09; 行为识别 WAF 强制修改密码 取消密码登录 segmentfault、知乎 个人用户安全建议 使用复杂密码不同网站使用…

python编程需要的电脑配置,python编程对电脑的要求

大家好&#xff0c;给大家分享一下python编程用什么笔记本电脑&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 不打游戏&#xff0c;只学编程。刚开始自学 Python小发猫伪原创,python下载需要花钱吗。 如果不搞机器学习的话&#xff0c;也…

centos7异常断电重启丢失系统引导。

起因&#xff1a; 公司机房意外断电&#xff0c;服务器断电异常关机&#xff0c;次日到达公司启动服务器&#xff0c;无法正常进入系统。 报错1&#xff1a; i8042: No controller found 报错2&#xff1a; Failed to mount /sysroot 排查思路&#xff1a; 通过报错可以看出系…

Linux网络编程(高并发服务器)

文章目录 前言一、什么是高并发服务器二、使用多线程和多进程实现高并发服务器的思路三、多进程服务器代码编写四、多线程服务器代码编写总结 前言 本篇文章带大家学习Linux网络编程中的高并发服务器。首先我们需要了解什么是高并发服务器&#xff0c;然后是学习如何来编写高并…

python——案例24:输出日历

案例24&#xff1a;输出日历import calendar #导入日历 yearint(2023) #设定年 moonint(8) #设定月print(calendar.month(year,moon))

黑客入侵:福特汽车Sync3车机存在漏洞,黑客入侵可抹除系统数据

据福特汽车公告&#xff0c;他们发现部分2021年至2022年车型的Sync3车机存在Wi-Fi漏洞&#xff0c;该漏洞可能被黑客利用来入侵并抹除车机内的系统数据。这一漏洞源于福特车系中采用的WL18xx MCP驱动程序的内存缓冲区溢位漏洞&#xff0c;其漏洞编号为CVE-2023-29468。 这一发现…

产品经理:实现一个微信输入框

近期在开发AI对话产品的时候为了提升用户体验增强了对话输入框的相关能力&#xff0c;产品初期阶段对话框只是一个单行输入框&#xff0c;导致在文本内容很多的时候体验很不好&#xff0c;所以进行体验升级&#xff0c;类似还原了微信输入框的功能&#xff08;只是其中的一点点…

matlab保存图片

仅作为记录&#xff0c;大佬请跳过。 即可。 参考 感谢大佬博主文章&#xff1a;传送门

Java算法_ 检查对称树(LeetCode_Hot100)

题目描述&#xff1a;给你一个二叉树的根节点 &#xff0c; 检查它是否轴对称。root 获得更多&#xff1f;算法思路:代码文档&#xff0c;算法解析的私得。 运行效果 完整代码 /*** 2 * Author: LJJ* 3 * Date: 2023/8/17 8:47* 4*/ public class SymmetricTree {static class…

Journal of Cheminformatics投稿经验分享

期刊名: Journal of Cheminformatics期刊名缩写:J CHEMINFORMATICS期刊ISSN:1758-2946E-ISSN:1758-29462023年影响因子/JCR分区:8.6/Q1SCI分区: CHEMISTRY, MULTIDISCIPLINARY 化学综合3区COMPUTER SCIENCE, INFORMATION SYSTEMS 计算机:信息系统2区COMPUTER SCIENCE, I…

AgentBench:AI智能体的应用前景——生产端的应用

生产端的应用 相比于消费端,AI智能体作为生产力工具的潜力则更为巨大。在现实中,很多工作需要专业化的数据作为支撑,通用化大模型显然不能胜任,这就给专用型的AI智能体留下了空间。在实践中,人们已经用大模型训练了不少专用的AI智能体。比如,不久前北京大学团队发行了一…

linux内核异步内存回收的另一个思路:基于冷热文件的冷热区域精准的回收冷文件页page(内核ko方案)

本文介绍的针对pagecache的异步内存回收方案与现有的思路有很大不同&#xff1a;内存回收的单位是一个个文件&#xff0c;再把文件的pagecache分成一个个小单元(或者叫区域)。提前判断出文件那些区域是频繁访问的(热区域)&#xff0c;哪些区域很少访问(冷区域)。异步内存回收线…

移动通信系统的LMS自适应波束成形技术matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ..................................................................... idxx0; while idxx&…

深入探索Java中的File类与IO操作:从路径到文件的一切

文章目录 1. File类的作用与构造方法2. File类常用方法&#xff1a;获取、判断和创建2.1 获取功能方法2.2 判断功能方法2.3 创建和删除功能方法2.4 目录的遍历方法 3. 递归&#xff1a;探索更深的层次代码示例&#xff1a;递归遍历文件夹 结论 &#x1f389;欢迎来到Java学习路…

AI极客日报0817 - 微软、亚马逊如何借助AI提升用户体验?

&#x1f440;AI 日报合集 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; 曾经&#xff0c;很多企业对ChatGPT的开放性表示担忧。如今&#xff0c;这些顾虑即将成为过去——微软带来了一个答案&#xff0c;推出了名为Azure ChatGPT的私有开源版本。那么&#xff0c;这一新版…

nodejs+vue+elementui多媒体作品信息共享平台开发_s2uq7

武理多媒体信息共享平台主要有管理员和用户两个功能模块。以下将对这两个功能的作用进行详细的剖析。 管理员模块&#xff1a;管理员是系统中的核心用户&#xff0c;管理员登录后&#xff0c;可以对后台系统进行管理。主要功能有个人中心、用户管理、作品分类管理、作品信息管理…

数据分析工具都有哪些?

简单介绍一下&#xff0c;数据分析是指适当的统计分析方法对收集来的大量数据进行分析&#xff0c;将它们汇总和理解消化&#xff0c;以求最大化地开发数据的功能&#xff0c;发挥数据的作用。 那么数据分析有哪些工具呢&#xff0c;是不是都需要掌握&#xff1f;当然不是的&am…

嵌入式设备应用开发(程序、静态库、动态库、配置文件)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 一个程序要想正确地在设备上运行起来,那么基本步骤就是,首先编写好程序代码,接着用交叉编译器编译出来,最后将这个程序拷贝到嵌入式设备上。然后,我们可以通过console控制台的…