首页 > 其他分享 >链式二叉树的实现(c实现)

链式二叉树的实现(c实现)

时间:2023-05-27 17:02:28浏览次数:34  
标签:遍历 实现 BTNode 二叉树 链式 NULL root 节点

本篇博客主要写了如何完成二叉树的前,中,后序遍历,查找特定值的节点,计算最大深度等。都是对二叉树的一些基本操作。

二叉树基本操作头文件

typedef char BTDataType;

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

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode* root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
//是返回真否返回假
bool BinaryTreeComplete(BTNode* root);

创建一个二叉树

既然要学会对二叉树的操作,那么首要操作自然是要创建一个二叉树,创建一颗二叉树。

下面是我们要创建的一颗二叉树(图里面的N代表的是NULL即空节点)

链式二叉树的实现(c实现)_递归

创建一颗二叉树有两种方法

创建二叉树方法一

可以直接手撕一个二叉树

BTNode* Buynode(char a)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fai");
		return;
	}
	newnode->data = a;
	newnode->left = NULL;
	newnode->right = NULL;
}
BTNode* BinaryTreeCreate()
{
	//首先要创建一个节点
	BTNode* newnode = BuyNode('A');//先创建非空的节点
	BTNode* newnnode2 = Buynode('B');
	BTNode* newnode3 = BuyNode('C');
	BTNode* newnode3 = Buynode('D');
	BTNode* newnode4 = Buynode('E');
	BTNode* newnode5 = Buynode('F');
	BTNode* newnode6 = Buynode('G');
	BTNode* newnode7 = Buynode('H');
	newnode->left = newnnode2;//将每一个非空节点的左右孩子赋值
	newnode->right = newnode3;
	newnnode2->left = newnode3;
	newnnode2->right = newnode4;
	newnode3->left = NULL;//对于叶子节点直接将左右赋值为空
	newnode3->right = NULL;
	newnode4->left = NULL;
	newnode4->right = NULL;
	newnode3->left = newnode5;
	newnode3->right = newnode6;
	newnode5->left = NULL;
	newnode5->right = NULL;
	newnode6->left = NULL;
	newnode6->right = NULL;
	//这样我们就手撕了一个二叉树
}

当然这种办法不是很灵活,现在还可以通过将一个二叉树前序或是中序后序遍历的结果,放进数组里面。再通过读取数组里面的值创建数组。我先将上面那个二叉树前序遍历的结果写出来。

创建二叉树的方法二

前序遍历结果ABD##E#H##CF##G##

BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)//这里的pi指针作用就是用于读取数组的下标
{//这里我们要使用递归的方式去写,所以这里考虑的就是递归的结束出口
	if (*pi < n || a[*pi] == '#')//当下标大于数组长度,或是遇到'#'后即代表这里的这个节点应该为空
	{
		(*pi)++;
		return NULL;//我们就将空传递给上一个栈帧
	}
	BTNode* newnode = Buynode(a[*pi]);//使用数组里面的值创建一个节点
	(*pi)++;//现在数组里面上一个值已经被使用了,要使用的下一个值了
	newnode->left = BinaryTreeCreate(a, n, pi);//使用递归的方式为这个节点的左节点赋值
	newnode->right = BinaryTreeCreate(a, n, pi);//使用递归的方式为这个节点的右节点赋值
	return newnode;//最后将头节点返回
}

直接这么看代码的话是很难理解的,我下面就将递归展开图画(非完全)出来。

链式二叉树的实现(c实现)_递归_02

得到二叉树的节点总个数

任然是使用递归来实现。首先我们要考虑到我们要求的是整个二叉树的所有非空节点个数,所以当我们读取到一个非空节点的时候,我们就要往上返回1,直到读取到NULL的时候返回0。

下面是函数实现:

// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;//如果节点为空向上返回0
	}
	//如果这个节点不为空就要去计算这个节点的左右子树有多少节点求出来
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

链式二叉树的实现(c实现)_递归_03

我们依旧使用递归展开图(非完全)来理解这个代码。

链式二叉树的实现(c实现)_递归_04

上面的两幅图我都只画了二叉树的一方的子树而且也没有画完请见谅。我也推荐自己去动手画一下这样的展开图能让我们更易于理解递归的运行方式

得到二叉树的叶子节点个数

// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
	//首先要知道叶子节点也就是没有左右子树的根节点
	if (root == NULL)
		return 0;//当遇到的节点为空时照旧返回0
	if (root->left == NULL && root->right == NULL)
		return 1;
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

链式二叉树的实现(c实现)_二叉树_05

这里我就不用物理递归展开图了,我使用逻辑展开图来解释。

链式二叉树的实现(c实现)_子树_06

我们只需要考虑到如果该节点为空节点,自然不是叶子节点返回0,如果该节点是叶子节点返回1,如果该节点既不是空节点,也不是叶子节点,就去这个节点的左右子树里面去寻找。

二叉树第k层节点个数

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
		return 0;//和上面的函数一样当所选节点为空时,代表的自然也不是第k层的节点个数。
	//对于这个函数的递归我们假设要求的是第4层的节点个数,那么对于第一层而言要求的是第四层的节点个数,对于第二层而言要求的也就是第三层的节点个数(这里将原本的第二层变为了第一层)
	//依次而下那么对于原本第三层的节点而言要求的也就是第二层的节点个数
	if (k == 1)
		return 1;
	//如果该节点不满足上面的条件就去该节点的左右子树去寻找。
	//这个函数运用了一个相对的概念,对于第三层而言要求的第四层也即是第二层。
	//所以当该节点即不为空也不是目标节点的时候我们就直接,去该节点的左右子树去寻找,而对于左右字数而言要找的第k层自然会少1
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

下面我还是使用逻辑递归展开图来表示。

链式二叉树的实现(c实现)_子树_07

链式二叉树的实现(c实现)_递归_08

二叉树查找值为x的节点

这里有两种实现的方法,先说思路对于二叉树上的节点,如果该节点为NULL时返回的肯定也是NULL。

如果该节点的值等于我们要寻找的值那就直接返回这个节点指针即可。如果该节点即不是空,也不是我们要寻找的值那么,就要递归使用函数去寻找该节点的左子树或是右子树中是否存在目标节点。

寻找值实现方法一

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	if (BinaryTreeFind(root->left, x))//如果该节点的左子树没有我们要寻找的值那就不返回
		return BinaryTreeFind(root->left, x);
	if (BinaryTreeFind(root->right, x))//如果我们在根节点的右子树里面找到了要寻找的值
		return BinaryTreeFind(root->right, x);//我们就将这个值返回
	return NULL;//如果都没有我们就直接返回空
}

这种写法是很不推荐的,这种写法对于不大的二叉树是不会有影响的。但是如果这颗树很大呢。我们这里就用一棵三层的树来递归一下上面的这个代码。

下面我给出递归展开图


链式二叉树的实现(c实现)_递归_09

修改方式也很简单,只需要每一次都将递归结果保留下来即可。

寻找值实现方法二

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;//如果该节点为空直接返回NULL
	if (root->data == x)
	{
		return root;//将满足要求的节点指针返回
	}
	BTNode* ret = BinaryTreeFind(root->left, x);//去该节点的左子树种寻找是否存在满足条件的节点
	if (ret)
		return ret;//如果从左子树里面寻找到的值不是空代表找到了直接返回这个节点指针
	ret = BinaryTreeFind(root->right, x);//左子树种如果不存在就去右子树种寻找
	if (ret)
		return ret;
	return NULL;//如果左子树和右子树种都不存在则直接返回NULL代表没有找到
}

运行截图:

链式二叉树的实现(c实现)_二叉树_10


二叉树的前中后序遍历

我们以下面的这棵二叉树举例:

链式二叉树的实现(c实现)_递归_11

前序遍历也就是我们先打印跟节点的值再去打印左右子树,这里假设空节点会打印N

那么上面的这棵树前序遍历的结果就是:A B D N N E N H N N C F N N G N N

和在数组里面的值一样。

那么中序遍历也就是先访问左子树,在访问根节点,最后方位右子树

那么上面这棵树中序遍历的结果也就是:N D N B N E N H N A N F N C N G N

那么后序遍历也就是先访问左节点,再访问右节点,最后再访问根节点

那么上面的这棵树后续遍历的结果也就是: N N D N N N H E B N N F N N G C A

写出代码之后再来和这些答案作对比。

前序遍历

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%c ", root->data);
	BinaryTreePrevOrder(root->left);//去遍历左子树
	BinaryTreePrevOrder(root->right);//去遍历右子树
}

运行截图:

链式二叉树的实现(c实现)_递归_12

和我们分析的答案一样

中序遍历

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	BinaryTreeInOrder(root->left);//先去遍历左子树
	printf("%c ", root->data);//读取根节点
	BinaryTreeInOrder(root->right);//遍历右子树
}

运行截图:

链式二叉树的实现(c实现)_递归_13

依旧和上面的答案一致

后序遍历

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%c ", root->data);
}

运行截图:

链式二叉树的实现(c实现)_二叉树_14

依旧和上面的答案一样。

二叉树的层序遍历

二叉树的层序遍历,从名字也能看出来要求一层一层的去遍历二叉树。

以上面的那棵二叉树为例它的层序遍历答案也就是 A B C D E F G H。

那么要怎么达成这种效果呢?这里我们就要使用我们队列的机构了,首先队列的特点是先进先出,即你先进入队列中的数据一定是先出来的。下面我画图来解释一下如何运用队列来实现


链式二叉树的实现(c实现)_二叉树_15

链式二叉树的实现(c实现)_子树_16

链式二叉树的实现(c实现)_递归_17

链式二叉树的实现(c实现)_子树_18

需要注意队列里面储存的并不是节点而是节点指针,还有就是我们将队列顶部的元素删除释放的并不是,二叉树节点的空间,二叉树的节点任然可以继续访问。下面是代码实现。(如何实现队列请参考我前面的博客)。

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	//既然要使用队列首先我们就要有一个队列
	Queue h;
	QueueInit(&h);//初始化队列使其能够使用
	if (root)//判断根节点是否为空,不为空则将根节点放入队列中
	{
		QueuePush(&h, root);
	}
	while (!QueueEmpty(&h))//当队列不为空的时候循环就继续
	{
		BTNode*front = QueueFront(&h);//获取队顶的元素
		QueuePop(&h);//删除队顶的元素
		printf("%c ", front->data);
		if (front->left)
			QueuePush(&h, front->left);//队顶元素的左孩子不为空将左孩子放入队列中
		if (front->right)
			QueuePush(&h, front->right);//原理同上
	}
}

链式二叉树的实现(c实现)_递归_19

判断二叉树是否为完全二叉树

首先完全二叉树是一种特殊的二叉树,它的每个非叶子节点都有左右两个子节点,并且除了深度最深的层次外,其他层次的节点都是满足节点数最大值的状态。

更具体地说,一棵深度为h的二叉树,如果除了第h层外,其他各层的节点数都达到了最大值,且第h层所有节点都集中在最左边,那么这棵二叉树就是一棵完全二叉树。其中,最大值是2^(h-1)个节点。

那么怎么去判断一颗树是否为完全二叉树呢?我们依旧要依靠队列来实现,

同层序遍历一样我们先将二叉树的节点放入队列中,然后我们获取队顶的元素判断这个元素是否为空(这里的队列即使是空节点我们也需要放入),如果这个节点为空那么停止放元素去判断,队列中是否存在非空元素,如果队列中还存在非空元素则直接返回false。反之返回true。下面依旧通过画图来解释

链式二叉树的实现(c实现)_子树_20


链式二叉树的实现(c实现)_二叉树_21

链式二叉树的实现(c实现)_子树_22

链式二叉树的实现(c实现)_子树_23

如果B的左边还有一个节点并且后面已经没有节点了。此时的二叉树任然是完全二叉树,那么反映到代码中就是在C的后面任然会有一个非空节点,不会对结果造成影响。如果c的左边还有一个孩子,这个孩子自然不会出现在C的后面而是会出现在NULL的后面,那么当进入最后一步的时候,我们就会发现队列中

出现了一个非空的元素证明这不是一颗完全二叉树。

下面是代码实现。

// 判断二叉树是否是完全二叉树
//是返回真否返回假
bool BinaryTreeComplete(BTNode* root)
{
	Queue h;//创建一个队列
	QueueInit(&h);//初始化队列
	QueuePush(&h, root);//将根节点放入队列中
	while (!QueueEmpty(&h))
	{
		BTNode* front = QueueFront(&h);//获取队顶的元素
		QueuePop(&h);//删除队顶的元素
		if (front == NULL)
			break;//如果队顶的元素为空则要进行最后一步判断队列中剩余元素是否为空
		else//不为空则将该节点的左右孩子放入队列中
		{
			QueuePush(&h, front->left);
			QueuePush(&h, front->right);
		}
	}
	//进行到这里一定是在队顶遇到了NULL
	//需要我们进行最后一步判断队列中是否还存在非空的接待你
	while (!QueueEmpty(&h))//在队列为空之前都要进行判断
	{
		BTNode* front = QueueFront(&h);
		QueuePop(&h);//删除队顶元素
		if (front)//如果front非空则代表不为完全二叉树
		{
			return false;
		}
	}
	return true;
}

链式二叉树的实现(c实现)_子树_24

得到二叉树的最大深度

int maxDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}//如果这个节点为空,代表不存在这一层返回的是0
	int lefthigh = maxDepth(root->left) + 1;//计算左子树的最大深度,并且加上自己这一层
	int righhight = maxDepth(root->right) + 1;//原理同上
	return lefthigh > righhight ? lefthigh : righhight;//返回深度深的那一层
}

这里依旧有另外一种写法就是不记录高度的写法,但是那种写法和在查找那一节所说的缺点是一样的这里也就不写出了。

链式二叉树的实现(c实现)_子树_25


最后感谢您能阅读我的这篇博客,希望对您能有所帮助,如果发现了错误欢迎评论区指正。



标签:遍历,实现,BTNode,二叉树,链式,NULL,root,节点
From: https://blog.51cto.com/u_15838996/6362641

相关文章

  • go语言中如何实现同步操作呢
    1.简介本文探讨了并发编程中的同步操作,讲述了为何需要同步以及两种常见的实现方式:sync.Cond和通道。通过比较它们的适用场景,读者可以更好地了解何时选择使用不同的同步方式。本文旨在帮助读者理解同步操作的重要性以及选择合适的同步机制来确保多个协程之间的正确协调和数据共享......
  • LRU牛客比较简单的实现
    https://www.nowcoder.com/practice/5dfded165916435d9defb053c63f1e84?tpId=295&tqId=2427094&ru=/exam/oj&qru=/ta/format-top101/question-ranking&sourceUrl=%2Fexam%2FojpublicclassSolution{Map<Integer,Integer>map;intcapacit......
  • kali2021.2实现VNC
    云安全(一)VNC连接文章目录云安全(一)VNC连接前言一、VNC是什么?二、使用步骤1.下载VNC平台2.修改远程密码3.更改xstartup文件内容4.安装RealVNC总结前言之所以要出这个博客的原因是当前的课本资料太老了,我填坑填了巨多,所以呕心沥血出了这个教程,学霸就直接看更改xstartup就好。利用VNC......
  • java实现导入word模板导入试题
    ​ 最近有一个项目需要将一个word文档中的试题数据导入,并存储到数据库中。试题类型包括:单选题、多选题、判断题、填空题、简答题。支持图片导入(我的这篇是借鉴JAVA实现Excel、Word模板导入-JAVA-华仔部落,javapoi解析上传word试卷(题库管理系统)-爱码网)这两位大神的。废话......
  • Springboot+Guava实现单机令牌桶限流
    令牌桶算法系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。=================......
  • m基于FPGA的PID控制器实现,包含testbench测试程序,PID整定通过matlab使用RBF网络计算
    1.算法仿真效果vivado2019.2、matlab2022a仿真结果如下:    2.算法涉及理论知识概要        PID控制器产生于1915年,PID控制律的概念最早是由LYAPIMOV提出的,到目前为止,PID控制器以及改进的PID控制器在工业控制领域里最为常见。PID控制器(比例-积分-微分控制器......
  • java中HashMap的实现原理
    HashMap是Java中常用的一种存储结构,它通过哈希表实现了快速查找数据的功能,下面是它的具体实现原理:HashMap内部存储结构HashMap的内部实现是一个数组和一个链表组成的。数组称为哈希表,用于保存实际存储的数据,链表则用于处理哈希冲突,即不同的键值对可能会被存储到哈希表的同一个位置......
  • 3.3 线性回归的简洁实现
    importnumpyasnpimporttorchfromtorch.utilsimportdatafromd2limporttorchasd2lfromtorchimportnn#nn是神经网络(NeuralNetworks)的缩写3.3.1生成数据集true_w=torch.tensor([2,-3.4])#与上一节类似生成数据集true_b=4.2features,labels=......
  • 一个用栈实现的括号匹配
    #include<iostream>#include<cstring>#include<stack>usingnamespacestd;intmain(){stringinput;cin>>input;inta=input.length();stack<char>stk;if(a%2!=0){cout<<"False"&......
  • HPL测试的配置(依赖于BLAS),通过OpenMpi进行实现
    1.1虚拟机的配置1.1.1Linux光盘映像文件由于对于Ubuntu系统更为熟悉,所以选择了最新版的Ubuntu系统作为Linux发行版。1.1.2Hypervisor由于之前一直使用VMware,对其中操作熟悉,因此选择VMware作为Hypervisor1.2搭建集群并安装相关程序1.2.1创建虚拟机以上为虚拟......