【数据结构初阶】链式二叉树接口实现超详解

news2024/9/21 22:00:20

文章目录

  • 1. 节点定义
  • 2. 前中后序遍历
    • 2. 1 遍历规则
    • 2. 2 遍历实现
    • 2. 3 结点个数
      • 2. 3. 1 二叉树节点个数
      • 2. 3. 2 二叉树叶子节点个数
      • 2. 3. 3 二叉树第k层节点个数
    • 2. 4 二叉树查找值为x的节点
    • 2. 5 二叉树层序遍历
    • 2. 6 判断二叉树是否是完全二叉树
  • 3. 二叉树性质


1. 节点定义

用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址,其基本结构如下:

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;				//数据
	struct BinaryTreeNode* left;	//左孩子
	struct BinaryTreeNode* right;	//右孩子
}BTNode;

链式二叉树的创建方式比较复杂,为了更好地对接口进行调试,我们先手动创建一棵链式二叉树进行测试:

//创建节点
BTNode* BuyBTNode(int val)
{
	BTNode * newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = val;
	newnode->left = NULL;
	newnode->right = NULL;
	return newnode;
}
BTNode * CreateTree()
{
	BTNode * n1 = BuyBTNode(1);
	BTNode * n2 = BuyBTNode(2);
	BTNode * n3 = BuyBTNode(3);
	BTNode * n4 = BuyBTNode(4);
	BTNode * n5 = BuyBTNode(5);
	BTNode * n6 = BuyBTNode(6);
	BTNode * n7 = BuyBTNode(7);
	//手动将他们连接起来成为一棵二叉树
	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n4->left = n5;
	n4->right = n6;
	n5->left = n7;
	
	return n1;
}

接下来就可以用这棵二叉树对接口进行测试。

2. 前中后序遍历

二叉树不同于之前的顺序结构,不能直接进行依次遍历,必须按照一定的规则进行遍历
注:堆也是一种二叉树,但是堆不能进行遍历,所以没有这方面的考虑。
另外二叉树的链式结构是一个递归的结构,几乎所有的接口实现都需要用到递归的思想。
1

2. 1 遍历规则

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

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

我们以前序遍历解释一下怎么遍历:
2
从根节点1开始遍历,先输出根节点数据1,然后来到左孩子2,输出2,来到2的左孩子3,输出3,来到3的左孩子NULL,回退至3,来到3的右孩子NULL,再回退,3节点遍历完毕,回退至2,来到2的右节点NULL,回退至2,2节点遍历完毕,回退至1,来到1的右孩子4,先输出4,再来到4的左孩子4,输出4,遍历左右俩孩子都为空,回退至上面的4,来到4的右孩子5,输出5后,俩孩子都为空,再最终回退至1,根节点遍历完成,该二叉树遍历完成。
前序遍历结果:123456
中序遍历结果:321546
后序遍历结果:315641

2. 2 遍历实现

在实现时,我们可以把每一个孩子都看成一个二叉树的根节点,以方便递归遍历。

//前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
	if (!root)
		return;
	printf("%d ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}
//后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (!root)
		return;
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
	printf("%d ", root->data);
}
//中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (!root)
		return;
	BinaryTreePrevOrder(root->left);
	printf("%d ", root->data);
	BinaryTreePrevOrder(root->right);
}

2

2. 3 结点个数

2. 3. 1 二叉树节点个数

int BinaryTreeSize(BTNode* root);

二叉树中只有结点,没有把数据个数存储起来,那么我们要怎么获取二叉树中元素的个数呢?
有两种思路,一个是创建全局变量,然后通过任意一种遍历方式遍历二叉树,每遍历一次都让这个变量++,那么就能得到节点的个数了,但是这样会带来两个问题:

  1. 全局变量每次都要归0,但是由于是递归进行遍历的,所以需要写一个子函数用于递归
  2. 在函数内部调用全部变量可能会存在危险,因为这个变量是任何函数都能访问并修改的

基于这样的原因,我们不采用这个方式。
而是通过计算这个节点本身+左子树节点的个数+右子树节点的个数的方式进行递归。

int BinaryTreeSize(BTNode* root)
{
	if (!root)
		return 0;
	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);	//1就是该节点本身,再加上左右两棵子树
}

2. 3. 2 二叉树叶子节点个数

int BinaryTreeLeafSize(BTNode* root);

叶子节点的特点就是root->left == NULL && root->right == NULL,所以只要加上一个判断是否要+1,其他的就和计算节点个数的思路是一样的。

int BinaryTreeLeafSize(BTNode* root)
{
	//如果是叶子节点,就没必要继续向下传递了,直接回归就可以了
	if (!root)
		return 0;
	if (root->left == NULL && root->right == NULL)
		return 1;
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

2. 3. 3 二叉树第k层节点个数

int BinaryTreeLevelKSize(BTNode* root, int k);

在传递时增加一个参数k,来判断递归到哪一层了,如果是目标层数,就+1回归,其他与计算二叉树叶子节点个数思路一致。

int BinaryTreeLevelKSize(BTNode* root, int k)
{
	//同理,到目标层数之后就不需要继续向下传递了
	if (!root)
		return 0;
	if (k == 1)
		return 1;
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

2. 4 二叉树查找值为x的节点

BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

在二叉树中遍历并对比数据,如果找到了就返回这个节点的指针,没找到就返回NULL

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (!root)
		return NULL;
	if (root->data == x)
		return root;
	//需要分别判断左右子树中有没有这个数据
	//左
	BTNode* leftfind = BinaryTreeFind(root->left,x);
	if (leftfind)
		return leftfind;
	//右
	BTNode* rightfind = BinaryTreeFind(root->right,x);
	if (rightfind)
		return rightfind;
	//没找到,返回空
	return NULL;
}

2. 5 二叉树层序遍历

除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历
设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
实现层序遍历需要额外借助队列这一数据结构。
1
层序遍历就是一层一层地从左到右地进行遍历,比如上面这棵二叉树的层序遍历就是:1 2 3 4 5 6 7

大致思路为:创建一个数据类型为BTNode*的队列,先将根节点入队列,然后分别将它的左右孩子入队列,接着出队列一次。然后把队头的左右孩子(如果不为NULL)入队列,再出队列一次,每次出队列前都要把它的值打印出来,循环以上过程直到队列为空。

注:这里的队列不再实现,可以看这篇博客。

void BinaryTreeLevelOrder(BTNode* root)
{
	//二叉树判空
	if (!root)
		printf("null\n");
	//创建队列并初始化
	Queue qu;
	QueueInit(&qu);
	//把根节点入队列
	QueuePush(&qu,  root);
	while (!QueueEmpty(&qu))
	{
		//将队头的左右子树入队列,并将队头数据打印,再出队列一次
		BTNode* tmp = QueueFront(&qu);
		QueuePop(&qu);
		printf("%d ", tmp->data);
		if (tmp->left)
			QueuePush(&qu, tmp->left);
		if (tmp->right)
			QueuePush(&qu, tmp->right);
	}
	//销毁队列
	QueueDestroy(&qu);
	printf("\n");
}

2. 6 判断二叉树是否是完全二叉树

bool BinaryTreeComplete(BTNode* root);

完全二叉树的特点是第X层没有完全放满时,X+1层不能有节点,且没有满的层的节点必须是从左往右排布的。

我们可以借助层序遍历来判断二叉树是不是完全二叉树。
步骤为:就算节点为空也要入队列,直到出队列时遇到了空节点时,开始连续出队列,如果队列中剩下的元素中有非空的节点,就说明不是完全二叉树

我们以这棵二叉树为例:
2
当轮到4的左孩子(尽管不存在,但先这么理解一下)出队列时,开始不再入队列,只出队列并判断是否为空,这时队列中还有4的右节点7,那就判断出来了二叉树不是完全二叉树。

如果在第一次出队列的为空节点的之后的节点都是空节点(比如上面这个二叉树没有节点7),那就是完全二叉树。

bool BinaryTreeComplete(BTNode* root)
{
	assert(root);
	Queue qu;
	QueueInit(&qu);
	QueuePush(&qu, root);
	while (!QueueEmpty(&qu))
	{
		BTNode* tmp = QueueFront(&qu);
		//如果出队列的是空节点,就停止这个循环
		if (!tmp)
			break;
		QueuePop(&qu);
		QueuePush(&qu, tmp->left);
		QueuePush(&qu, tmp->right);
	}
	//这个循环只出不入
	while (!QueueEmpty(&qu))
	{
		//如果队列中还有空节点,就说明不是完全二叉树
		if (QueueFront(&qu))
		{
			QueueDestroy(&qu);
			return false;
		}
		QueuePop(&qu);
	}
	QueueDestroy(&qu);
	return true;
}

3. 二叉树性质

  1. 对任何一棵二叉树,如果度为 0 的叶结点个数为n0,度为 2 的分支结点个数为n2 ,则有
    n0 = n2+1
    2
    证明:
    假设一个二叉树有 a 个度为 2 的节点,b 个度为 1 的节点,c 个叶节点,则这个二叉树的边数是 2a+b
    另一方面,由于共有 a+b+c 个节点,所以边数等于 a+b+c-1
    结合上面两个公式:
    即 2a+b = a+b+c-1,即 a=c-1

题目练习

  1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )
    A 不存在这样的二叉树
    B 200
    C 198
    D 199
    有199个度为2的节点,那么在二叉树中,度为0的节点有199+1也就是200个,选B。

  2. 在具有 2n 个结点的完全二叉树中,叶子结点个数为()
    A n
    B n+1
    C n-1
    D n/2
    设叶子节点有x个,那么度为2的节点为x-1个。有2n个节点,所以度为1的节点有2n-2x+1条,而完全二叉树中度为1的节点只有0或1个,但是这个二叉树的结点个数为偶数(由于根节点有1个,而其他的除了最后一层的结点个数都是偶数),所以度为1的结点个数为1。解方程2n-2x+1=1就可以得出x=n。选A。

  3. 一棵完全二叉树的结点数位为531个,那么这棵树的高度为()
    A 11
    B 10
    C 8
    D 12
    完全二叉树每一层的节点个数为2n-1个,那么根据等比数列的求和公式,完全二叉树前n层的节点个数为2n-1,解方程2n-1>=531可得,n为10,选B。

  4. 一个具有767个结点的完全二叉树,其叶子结点个数为()
    A 383
    B 384
    C 385
    D 386
    设叶子节点有x个,那么度为2的节点为x-1个,度为1的节点为768-2x个,通过第2题的分析我们可知度为1的节点为0个,就可以算出x=384,选B。

数据结构初阶的二叉树就到这里,想必你会发现这个二叉树我们没有实现插入删除的接口,因为二叉树的插入删除使用C语言实现过于复杂,会在高阶数据结构中讲解。

谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章

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

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

相关文章

日志系统第三弹:日志消息和格式化模块的实现

日志系统第三弹:日志消息和格式化模块的实现 一、日志消息模块的实现二、日志格式化模块的设计1.格式化占位符的约定2.如何打印1.各种零件类1.抽象类2.简单的零件类3.日期零件类4.非格式化数据零件类 2.Formatter 3.如何解析 三、日志格式化模块的实现1.解析函数2.c…

一文详解Unity下RTMP推送|轻量级RTSP服务|RTSP|RTMP播放模块说明

技术背景 好多开发者,对Unity下的模块,不甚了解,实际上,除了Windows/Linux/Android/iOS Native SDK,大牛直播SDK发布了Unity环境下的RTMP推流|轻量级RTSP服务(Windows平台Linux平台Android平台&#xff09…

Windows安装Oracle11gR2(图文教程)

本章教程,记录在Windows10上安装Oracle11gR2过程。 一、下载安装包 通过网盘分享的文件:oracle11g 链接: https://pan.baidu.com/s/15ilciQ5NlKWtClklmdAH_w?pwds4dd 提取码: s4dd 二、下载并解压文件 将网盘中的安装包文件下载到本地,在此之…

谷歌收录查询工具,好用的谷歌收录查询工具应具备的这5个特性

在探讨如何高效利用谷歌收录查询工具以优化网站可见性和搜索引擎排名时,好用这一标准往往涵盖了工具的准确性、易用性、功能全面性以及对搜索引擎算法变化的适应性等多个方面。 1.准确性 首先,一款好用的谷歌收录查询工具必须能够提供高度准确的数…

C Prime Plus 第6章习题

你该逆袭了 红色标注的是:错误的答案 蓝色标注的是:正确的答案 绿色标注的是:做题时有疑问的地方 橙色标注的是:答案中需要着重注意的地方 练习题 一、复习题1、2、3、4、5、我的答案:错误正确答案: 6、7、…

窥探 引用拷贝、浅拷贝、深拷贝 的那些事 (clone版)

谁家玉笛暗飞声 散入春风满洛城 往期回顾✨内部类 目录✨ 引用拷贝 介绍 总结 浅拷贝 介绍 浅拷贝的步骤 深拷贝 介绍 引用拷贝 介绍 引用拷贝就是我们常用的 “赋值” ,只是复制了原对象的引用,即两个对象指向同一块内存堆地址。修改其中的一个对象会影…

【图灵完备 Turing Complete】游戏经验攻略分享 Part.6 处理器架构2 函数

新的架构来了,本游戏的最后一个攻略分享,最后汇编部分无非是对于操作码的熟练,硬件没有问题,那么也就无关痛痒了。 汇编实现,两数相或和两数相与非一起相与即可。 八位异或器,整就完事了。 有手就行。 利…

【梯度下降算法学习笔记】

梯度下降单参数求解 经过之前的学习我们来引入梯度下降的理念 α \alpha α为学习率 w 1 w 初 − α ∂ J ( w ) ∂ w w_1w_初-\alpha\frac{\partial J(w)}{\partial w} w1​w初​−α∂w∂J(w)​ w 2 w 1 − α ∂ J ( w 1 ) ∂ w 1 w_2w_1-\alpha\frac{\partial J(w_1)}…

国庆电影扎堆来袭,AI智能体帮你推荐必看佳片!(附制作教程)

大家好,我是凡人。 今天看到新闻,发现国庆有10部影片要扎堆儿上映,对于选择困难症的我属实有点难选,同时也想避开一些坑省的浪费金钱和时间。 本着不知道就问AI的习惯,想问问大模型怎么看,但做了简单的交…

MySQL 优化器:理解与探秘

在 MySQL 数据库的世界里,优化器扮演着至关重要的角色。它就像是一位幕后的魔法师,默默地为数据库的高效运行贡献着力量。那么,MySQL 优化器究竟是什么?它又是如何工作的呢?让我们一起来揭开它的神秘面纱。 一、MySQL…

行业人工智能研究-Python自监督方式学习图像表示算法

学术界人工智能研究落后于工业界 摘要 行业或工业界在人工智能研究上超出学术界,并占据着大量的计算力,数据集和人才诱人的薪水和明朗的预期吸引大量人才离开学术界,涌入行业或工业界即使,比如Meta开源其人工智能模型&#xff0…

二分查找算法(1) _二分查找_模板

个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 二分查找算法(1) _二分查找模板 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记,欢迎大家在评论区交流讨论💌 1. 二…

掌握Android开发新趋势:Jetpack与现代架构技术全解析

随着Android开发技术的不断进步,Jetpack和现代架构技术已成为构建高效、可维护应用的关键。本文将为您介绍一套全面的学习资料,包括大纲、PDF文档、源代码以及学习视频,帮助您深入理解Jetpack核心库、架构组件以及现代开发工具。 内容&#…

linux-基础知识4

网络连接性测试 ping ping可以用来测试本机与目标主机的连通速度网络稳定性 ping -c 5 -s 1024 目标主机ip地址 -c 表示ping包的个数,linux如果缺省-c会一直ping下去,windows平台的选项是-n -s指定ping发送数据的字节数默认是84字节。windows的是-l 没有问题时会之…

如何设计出一个比较全面的测试用例

目录 1. 测试用例的基本要素(不需要执行结果) 2. 测试用例的给我们带来的好处 3. 用例编写步骤 4. 设计测试用例的方法 4.1 基于需求进行测试用例的设计 4.2 具体的设计方法 1.等价类 2.边界值 3.判定表(因果图) 4.正交表法 5.场景设计法 6.错误猜测…

IO流体系(FiletOutputStream)

书写步骤: 1.创建字节输出流对象 细节1:参数是字符串表示的路径或者是File对象都是可以的 细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。 细节3:如果文件已经存在,则会清空文件 2.写数据 细节:write方法的参数…

Python | 绘制核密度图

写在前面 台风天,适合敲代码。前两天正好看到一个有意思的核密度图,使用的是seaborn绘制的。之前了解过这个包,但是一致没有去绘制相关的图,这次正好去学习一下相关的函数。 绘制结果如下所示: 主要两个有意思的地方…

二维光场分析

一、单色光波长的复振幅表示 实波函数 复波函数 复振幅 由于时间因子相同,可以用复振幅来描述 光强 1.1 球面波的复振幅(单色点光源发出的光波) 等相位面是同心球面,波矢处处与等相位面垂直,即 是 r = 1 处的振幅 发散球面波: 会聚球面波: <

828华为云征文 | 华为云X实例监控与告警管理详解

前言 随着云计算的飞速发展&#xff0c;越来越多的企业将业务部署在云平台上&#xff0c;云服务器实例的管理变得尤为重要。云实例的稳定性、性能及安全性&#xff0c;直接影响着业务的连续性与用户体验。为了确保这些目标的实现&#xff0c;监控与告警是关键手段。本文将详细…

2024华为杯研赛C题原创python代码+结果表(1-2问)|全部可运行|精美可视化

2024华为杯研赛C题原创python代码结果表&#xff08;1-2问&#xff09;&#xff5c;全部可运行&#xff5c;精美可视化 以下均为python代码&#xff0c;推荐用anaconda中的notebook当作编译环境 第一问&#xff1a; import pandas as pd import numpy as np import matplotl…