链式二叉树及二叉树各种接口的实现(C)

news2024/11/28 8:34:19
二叉树的性质
  1. 若规定根节点的层数为1,则一棵非空二叉树的第 i i i层上最多有 2 i − 1 2^{i-1} 2i1个结点.
  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2 h − 1 2^{h}-1 2h1
  3. 对任何一棵二叉树,如果度为0其叶结点个数为 n 0 n_{0} n0, 度为2的分支结点个数为 n 2 n_{2} n2,则有 n 0 = n 2 + 1 n_{0}=n_{2}+1 n0=n2+1
  4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度, h = log ⁡ 2 ( n + 1 ) h=\log_{2}(n+1) h=log2(n+1)
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为 i i i的结点有:
    1. i > 0 i> 0 i>0 i i i位置节点的双亲序号: ( i − 1 ) / 2 (i-1)/2 (i1)/2 i = 0 i=0 i=0 i i i为根节点编号,无双亲节点
    2. 2 i + 1 < n 2i+1<n 2i+1<n,左孩子序号: 2 i + 1 2i+1 2i+1 2 i + 1 ≥ n 2i+1\ge n 2i+1n否则无左孩子
    3. 2 i + 2 < n 2i+2<n 2i+2<n,右孩子序号: 2 i + 2 2i+2 2i+2 2 i + 2 ≥ n 2i+2\ge n 2i+2n否则无右孩子
      ![[Pasted image 20240927172102.png]]
链式结构的实现

![[Pasted image 20240927174228.png]]

每棵树都可以分解成根节点,根节点的左子树,根节点的右子树
这样可以从根节点开始一直往下拆解,直到这棵树的左右两个子树都是空树停止

二叉树的遍历

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
    ![[Pasted image 20240927175114.png]]
  • 前序
    要求把树拆成根节点,左子树,右子树
    遍历顺序:
    1,{2,(3,N,N),(N)},{4,(5,N,N),(6,N,N)}
  • 中序
    左子树,根,右子树
    遍历顺序:
    {(N,3,N),2,(N)},1,{(N,5,N),4,(N,6,N)}
    遍历顺序
  • 后序
    左子树,右子树,根
    遍历顺序:
    {(N,N,3),(N),2},{(N,N,5),(N,N,6),4},1
  • 层序
    一层一层走
    1,2,4,3,5,6

普通的链式二叉树没有意义

二叉树树的实现

链式树的定义
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	int val;
}BTNode;
  • 有左右两个子树节点
  • 还有val表示节点存的值
创建节点
BTNode* BuyNode(int x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node = NULL)
	{
		perror("malloc fail");
		exit(-1);	
	}

	node->val = x;
	node->left = NULL;
	node->right = NULL;

	return node;
}
  • malloc一个树节点的内存空间大小
  • 判断空间是否创建成功
  • 将节点的值置为x,左子树和右子树的指针置为空
二叉树的递归结构

递归调用展开图
![[Pasted image 20240929160449.png]]

先序中序后序遍历实现
void PrevOrder(BTNOde* root)
{
	if (root == NULL)
		return;

	printf("%d ", root->val);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

void InOrder(BTNOde* root)
{
	if (root == NULL)
		return;

	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right);
}

void PostOrder(BTNOde* root)
{
	if (root == NULL)
		return;

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);
}
  • 通过递归实现遍历二叉树
  • 先实现返回条件,当目前子树的根节点是空时,返回
  • 这里是双路递推,先是递推左子树,再递推右子树,前中后遍历分别在两次递推的前中后访问根节点的值
节点个数
  1. 局部静态变量
int TreeSize(BTNode* root)
{
	static int size = 0;
	if (root == NULL)
		return 0;
	else
		++size;

	TreeSize(root->left);
	TreeSize(root->right);

	return size;
}

静态变量和全局变量存在于静态区,所以各个位置的size指同一个size,因为都是从静态区去取的,不是栈帧里面都有的
不会每次都走初始化,局部的静态成员变量只会执行一次
但是当多次调用这个函数时,size会累加,不符合预期。每次调用都要初始化为0
目前生命周期是全局的,作用域是局部的
这是不对的,这样的treesize是一次性的。
2. 手动初始化

int size = 0;

int TreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	else
		++size;

	TreeSize(root->left);
	TreeSize(root->right);

	return size;
}

在全局初始化size为0,之后调用的时候手动将size置为0
会发生限制安全问题
3. 变为递归子问题
遇到根节点,想求这棵树的节点个数,返回它的两棵子树的节点个数,接着找子树的子树,一直递归直到子树为空
树的节点的个数等于左子树节点的个数加右子树节点的个数再加自己

int TreeSize(BTNode* root)
{
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

![[Pasted image 20241001113806.png]]

叶子节点个数

节点是空,返回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);
}

![[Pasted image 20241001130133.png]]

第k层的节点个数

当前树的第k层=左子树的第k-1层+右子树的第k-1层
一直到某一节点的第一层
如果为空,返回0;不为空,返回1

int TreeKLevel(BTNode* root, int level)
{
	assert(k > 0);

	if (root == NULL)
		return 0;

	if (k == 1)
	{
		return 1;
	}

	return TreeKLever(root->left, k-1) 
		+ TreeKLever(root->right, k-1);
}

![[Pasted image 20241001140506.png]]

销毁

遇到一个根节点,先销毁左子树,再销毁右子树,回来再销毁自己

void TreeDestroy(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	TreeDestroy(root->left);
	TreeDestroy(root->right);

	free(root);
}

不需要置空,形参的改变不影响实参

查找

如果节点为空,返回空
如果节点的值等于x,返回节点
左子树找到就不用到右子树找了

//二叉树查找值为x的节点
BTNode* TreeFind(BTNode* root, int x)
{
	if (root == NULL)
		return NULL;

	if (root->val == x)
		return root;

	return TreeFind(root->left, x)
		|| TreeFind(root->right, x);
}

这样返回的不是节点的指针,返回的是x在不在,是空指针或1的结果
如果这个函数返回的是bool值,只判断真和假

BTNode* TreeFind(BTNode* root, int x)
{
	if (root == NULL)
		return NULL;

	if (root->val == x)
		return root;

	BTNode* ret = NULL;
	ret = TreeFind(root->left, x);
	if (ret)
		return ret;

	ret = TreeFind(root->right, x);
	if (ret)
		return ret;

	return NULL;
}

如果左边找到了,就return一下
如果没找到,就去右边找,找到了就return
如果右边也没找到,就返回空

  1. 若x=3
    ![[Pasted image 20241001185739.png]]

  2. 若x=4
    ![[Pasted image 20241001191416.png]]

类似写法

BTNode* TreeFind(BTNode* root, int x)
{
	if (root == NULL)
		return NULL;

	if (root->val == x)
		return root;

	BTNode* ret = NULL;
	ret = TreeFind(root->left, x);
	if (ret)
		return ret;

	return TreeFind(root->right, x);

}
层序遍历

先进先出
根节点入队列,出队列的时候将它的两个左子节点和右子节点插入队列
上一层带下一层
因为要存的是树的指针,所以队列的数据类型要改为BTNode*
.h文件不会被编译,而会在.c文件里面展开,所以要包含队列的.h文件,include语句要在树的结构体的下面,这样.h文件可以找见树的定义

void LevelOrder(BTNode* root)
{
	Que q;
	QueueInit(&q);

	if (root)
		QueuePush(&q, root);
	
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		printf("%d ", front->val);
		if (front->left)
			QueuePush(&q, front->left);
		
		if (front->right)
			QueuePush(&q, front->right);

		QueuePop(&q);
	}
	printf("\n");
	
	QueueDestroy(&q);
}
判断完全二叉树

完全二叉树走层序遍历的特征
完全二叉树在满二叉树的基础上,前n-1层都是满的,最后一层不满
但是最后一层,从左到右是连续的
如果这棵树是完全二叉树,这棵树层序遍历都是连续的
即层序:非空节点是连续的就是完全二叉树;非空节点是不连续,中间有空节点,就是非完全二叉树
要判断是否连续,层序遍历的时候,把NULL也入进去
非完全二叉树,出现空了以后,一定还有非空在队列里面

int TreeComplete(BTNode* root)
{
		Que q;
	QueueInit(&q);

	if (root)
		QueuePush(&q, root);
	
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		if (front == NULL)
			break;

		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
		QueuePop(&q);
	}

	//已经遇到空节点,如果队列中后面还有非空,就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;
}
  • pop了以后还可以访问front节点,pop删除的是队列的节点,不会影响树的节点
  • 队列里有指针指向树的节点
  • 取front,相当于用一个front指针指向队头的节点
  • 队列为空才能结束,不是到那个节点结束
返回树的高度

对于根节点而言,如果求出了左子树的高度和右子树的高度
树的高度等于左子树和右子树高的那一个加1
空树的时候,高度取0

int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;

	return TreeHeight(root->left) > TreeHeight(root->right) ? TreeHeight(root->left) + 1 : TreeHeight(root->right) + 1;
}

这样写运算消耗很大,会重复计算,每棵树都是这样
所以要把每次计算出来的值保存下来

int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;

	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);

	return leftHeight > rightHeight ? leftHeight + 1: rightHeight + 1;
}

将比较大小封装成一个函数

int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	
	return fmax(TreeHeight(root->left), TreeHeight(root-<right)) + 1;
}

没有重复计算

深度优先广度优先

深度优先遍历:前序遍历,递归
广度优先遍历:层序遍历,队列配合

声明定义分离实现

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	int val;
}BTNode;

BTNode* BuyNode(int x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node = NULL)
	{
		perror("malloc fail");
		exit(-1);	
	}

	node->val = x;
	node->left = NULL;
	node->right = NULL;

	return node;
}

void PrevOrder(BTNOde* root)
{
	if (root == NULL)
		return;

	printf("%d ", root->val);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

void InOrder(BTNOde* root)
{
	if (root == NULL)
		return;

	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right);
}

void PostOrder(BTNOde* root)
{
	if (root == NULL)
		return;

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);
}

//节点个数
int TreeSize(BTNode* root)
{
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 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);
}

//第k层节点个数
int TreeKLevel(BTNode* root, int level)
{
	assert(k > 0);

	if (root == NULL)
		return 0;

	if (k == 1)
	{
		return 1;
	}

	return TreeKLever(root->left, k-1) 
		+ TreeKLever(root->right, k-1);
}

//二叉树销毁
void TreeDestroy(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	TreeDestroy(root->left);
	TreeDestroy(root->right);

	free(root);
}

//二叉树查找值为x的节点
BTNode* TreeFind(BTNode* root, int x)
{
	if (root == NULL)
		return NULL;

	if (root->val == x)
		return root;

	TreeFind(root->left, x);
	TreeFind(root->right, x);
}

void LevelOrder(BTNode* root)
{
	Que q;
	QueueInit(&q);

	if (root)
		QueuePush(&q, root);
	
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		printf("%d ", front->val);
		if (front->left)
			QueuePush(&q, front->left);
		
		if (front->right)
			QueuePush(&q, front->right);

		QueuePop(&q);
	}
	printf("\n");
	
	QueueDestroy(&q);
}

int TreeComplete(BTNode* root)
{
		Que q;
	QueueInit(&q);

	if (root)
		QueuePush(&q, root);
	
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		if (front == NULL)
			break;

		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
		QueuePop(&q);
	}

	//已经遇到空节点,如果队列中后面还有非空,就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;
}

int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	
	return fmax(TreeHeight(root->left), TreeHeight(root-<right)) + 1;
}

int main()
{
	//手动构建二叉树
	BTNode* node1 = BuyNode(1);
	BTNode* node1 = BuyNode(2);
	BTNode* node1 = BuyNode(3);
	BTNode* node1 = BuyNode(4);
	BTNode* node1 = BuyNode(5);
	BTNode* node1 = BuyNode(6);

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

	PrevOrder(node1);
	printf("\n");
	
	InOrder(node1);
	printf("\n");

	PostOrder(node1);
	printf("\n");

	return 0;
}

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

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

相关文章

Semantic Communication Meets Edge Intelligence——构造终端共享的知识图谱指导无线物联网通信中文本的传输

论文链接&#xff1a; IEEE Xplore Full-Text PDF:https://ieeexplore.ieee.org/stamp/stamp.jsp?tp&arnumber9979702 1. 背景 随着自动驾驶、智能城市等应用的发展&#xff0c;移动数据流量将大幅增加。传统的香农信息论&#xff08;CIT&#xff09;通信系统已接近其带…

内网穿透工具ngrok

写作背景 最近在公司内购淘了个MAC电脑&#xff0c;想当个Linux服务器起Docker搭建环境用&#xff0c;现在问题是如何在公网上能访问到MAC这个机器上的资源。 之前写了一篇文章Mac当作云服务器&#xff0c;你真的会搞吗 最近想重启一下这台老伙计了&#xff0c;发现ngrok还是…

CIKM 2024 | 时空数据(Spatial-temporal)论文总结

CIKM 2024于10月21号-10月25号在美国爱达荷州博伊西举行&#xff08;Boise, Idaho, USA&#xff09; 本文总结了CIKM 2024有关时空数据&#xff08;spatial-temporal data&#xff09;的相关论文&#xff0c;主要包含交通预测&#xff0c;插补&#xff0c;事故预测&#xff0c…

计算机网络——http和web

无状态服务器——不维护客户端 怎么变成有状态连接 所以此时本地建立代理—— 若本地缓存了——但是服务器变了——怎么办&#xff1f;

CSS元素堆叠

通常我们可能会认为 HTML 网页是个二维的平面&#xff0c;因为页面中的文本、图像或者其它元素都是按照一定顺序排列在页面上的&#xff0c;每个元素之间都有一定的间隙&#xff0c;不会重叠。然而&#xff0c;实际的网页其实是三维的&#xff0c;元素之间可能会发生堆叠&#…

《python语言程序设计》2018版第8章19题几何Rectangle2D类(中)-同志们我要起飞了

前言 昨天的原始绘制两个矩形的代码段draw_rec2原始draw_rec2运行结果我们不是上面往右转90.我怎么往左转90不对吗??? ☺️结果利用已建立完的Rectangle2D类来实现Rectangle2D类的代码可以找上集看,今天是锻炼的一天好几个倒立体式解锁了.祝大家愉快 经过昨天晚上的努力我终…

Python画笔案例-078 绘制 颜色渐变之coloradd

1、绘制纯 颜色渐变之coloradd 通过 python 的turtle 库绘制 颜色渐变之coloradd,如下图: 2、实现代码 绘制 颜色渐变之coloradd,以下为实现代码: """颜色渐变之coloradd.py本程序需要coloradd模块支持,请在cmd窗口,即命令提示符下输入pip install colorad…

VMware桥接模式无法连接网络

windows下打开控制面板&#xff0c;找到WLAN&#xff0c;记住下面的名称&#xff08;带有VMware的都是虚拟机的网卡&#xff0c;要找到物理主机的网卡&#xff09; 回到VMware&#xff0c;编辑——打开虚拟网络编辑器 桥接选择上面的WLAN下的网络名称&#xff0c;确定即可。&…

tortorise数据库迁移变化aerich

数据库迁移 使用场景&#xff0c;当需要修改定义的数据库中表的数据时&#xff0c;就可以利用aerich进行迁移改动 例如 class Asset(models.Model):aid fields.CharField(max_length50, pkTrue)asset_name fields.CharField(max_length150)target_name fields.CharField(…

GO网络编程(三):海量用户通信系统1:登录功能初步

一、准备工作 需求分析 1)用户注册 2)用户登录 3)显示在线用户列表 4)群聊(广播) 5)点对点聊天 6)离线留言 主界面 首先&#xff0c;在项目根目录下初始化mod&#xff0c;然后按照如下结构设计目录&#xff1a; 海量用户通信系统/ ├── go.mod ├── client/ │ ├──…

血液细胞计数与检测(BCCD)数据集教程

BCCD 数据集&#xff1a;血液细胞检测与计数-CSDN博客文章浏览阅读431次&#xff0c;点赞5次&#xff0c;收藏3次。BCCD 数据集&#xff1a;血液细胞检测与计数 BCCD_Dataset BCCD (Blood Cell Count and Detection) Dataset is a small-scale dataset for blood cells detecti…

U盘格式化别担心,数据恢复神器来了!

一、恢复数据的紧迫性和希望 别担心&#xff0c;小编我有幸深陷U盘数据丢失的境地&#xff0c;因此通过不懈的努力与反复试验&#xff0c;今日在此为广大读者分享一次趟雷后恢复数据工具的真实体验&#xff1b;当U盘格式化后&#xff0c;你可能会面临数据的丢失&#xff0c;但…

超简单 Flux 换背景工作流:结合最新 ControlNet 深度 Depth 模型

在本篇文章中&#xff0c;我们将深入探讨如何使用 Flux ControlNet Depth 模型进行换背景。这种方法是我之前基于 Flux 模型换背景工作流的简化版。虽然旧的工作流程功能强大&#xff0c;但它非常复杂且运行缓慢。今天&#xff0c;我们将学习一个更快速、更易用的替代方案。 F…

变电站红外检测数据集 1180张 变电站红外 标注voc yolo 13类

变电站红外检测数据集 1180张 变电站红外 标注voc yolo 13类 变电站红外检测数据集 名称 变电站红外检测数据集 (Substation Infrared Detection Dataset) 规模 图像数量&#xff1a;1185张图像。类别&#xff1a;13种设备类型。标注个数&#xff1a;2813个标注。 数据划分…

【全球顶级域名后缀】

数据时间: 2024.10.6 广告: 五分钟申请SSL证书 (手机电脑都能用) ["aaa","aarp","abarth","abb","abbott","abbvie","abc","able","abogado","abudhabi","ac"…

一项研究表明,只需一滴干血,新的检测技术或许可以在几分钟内发现癌症

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

视频加字幕用什么软件最快?12款工具快速添加字幕!

对于大多数同学来讲&#xff0c;剪辑中比较头疼的就是如何给视频加字幕和唱词啦&#xff0c;特别是用Pr或者FCXP等专业剪辑软件&#xff0c;加字幕也是特别费时的&#xff0c;哪怕是有批量添加的功能orz... 虽然关于这方面的内容已经很多啦&#xff0c;但是真正全面的内容还特…

unity 2d 近战攻击判定的三种方式以及精确获取碰撞点

精确获取碰撞点 核心是获取武器碰撞盒最顶点&#xff0c;然后获取敌人碰撞盒距离该点最近的点 /// <summary>/// 获取获取武器前端位置 碰撞盒最左或最右顶点/// </summary>/// <param name"collider"></param>/// <param name"…

如何把数组作为参数传递给函数(注意,只是传递数组名)?

直接上代码吧&#xff1a; template<class T, size_t nSize> void printArray(T(&Array)[nSize]) {T* pt Array;for (size_t n 0; n < nSize; n) {cout << *(pt n) << "\t";}cout << "\n"; } int main() {int ia[] {…

6款不错的本地大模型运行工具推荐

运行大型语言模型 (LLM)&#xff08;如 ChatGPT 和 Claude&#xff09;通常涉及将数据发送到 OpenAI 和其他 AI 模型提供商管理的服务器。虽然这些服务是安全的&#xff0c;但一些企业更愿意将数据完全离线&#xff0c;以保护更大的隐私。 本文介绍了开发人员可以用来在本地运…