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

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

时间:2023-06-03 12:03:46浏览次数:31  
标签:遍历 语言 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语言)_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语言)_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语言)_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语言)_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/6407675

相关文章

  • 靳宇灵 | FastAdmin多语言配置
    在FastAdmin中可以在任何位置(控制器、视图、JS)使用__('语言标识');调用语言包,如果语言标识不存在,则直接输出该语言标识。使用方法FastAdmin中的__函数和ThinkPHP中的lang函数在传参上有些许区别比如__('Mynameis%s',"FastAdmin");将会返回MynameisFastAdmin而如果采用Thin......
  • 【视频】R语言机器学习高维数据应用:Lasso回归和交叉验证预测房屋市场租金价格
    分析师:JunjunLi在这篇文章中,我们将着重探讨高维数据下的机器学习应用,以房屋市场租金价格预测为例。在实际生活中,房屋租金作为一个重要的经济指标,被广泛应用于城市规划、财务投资等方面的决策中。然而,如何准确地预测房屋租金价格却一直是一个具有挑战性的问题。本文将介绍如何使用L......
  • R语言状态空间模型和卡尔曼滤波预测酒精死亡人数时间序列|附代码数据
    最近我们被客户要求撰写关于状态空间模型的研究报告,包括一些图形和统计输出。状态空间建模是一种高效、灵活的方法,用于对大量的时间序列和其他数据进行统计推断摘要本文介绍了状态空间建模,其观测值来自指数族,即高斯、泊松、二项、负二项和伽马分布。在介绍了高斯和非高斯状态空间模......
  • R语言关联规则Apriori对抗肿瘤中药数据库知识发现研究
    肿瘤是近年来严重威胁人类的健康的疾病,据统计,目前大部分种类的肿瘤都呈现不同程度的上升趋势,中国因患肿瘤而死亡的人数约占全球肿瘤死亡总人数的1/4左右,人类正面临着肿瘤防治的新挑战。现代医学治疗肿瘤的手段和方式已经日臻完善,主要为手术配合放、化疗联合治疗。但传统西医治......
  • 【花雕学AI】ChatGPT的四大语言处理神器:文本生成、问答、创意生成和内容优化的技巧和
    引言:ChatGPT是一个人工智能聊天机器人,它可以理解和交流多种语言,例如中文、英文、日文、西班牙语、法语、德语等。它是由OpenAI开发的,基于GPT-3.5和GPT-4这两个大型语言模型。它不仅可以与用户进行对话,还可以根据用户的指示完成一些语言处理的任务,例如文本生成、问答、创意生成和内......
  • R语言APRIORI模型关联规则挖掘分析脑出血急性期用药规律最常配伍可视化|附代码数据
    最近我们被客户要求撰写关于关联规则的研究报告,包括一些图形和统计输出。本文帮助客户运用关联规则方法分析中医治疗脑出血方剂,用Apriori模型挖掘所选用的主要药物及其用药规律,为临床治疗脑出血提供参考脑出血急性期用药数据读取数据a_df3=read.xlsx("脑出血急性期用药最常配伍......
  • go语言中protobuf以及grpc的使用
    首先定义数据结构,保存为.proto文件syntax="proto3";packagetutorial;//Theprotocolcompilergeneratesaclassfromthefollowing.protofilewith//methodsforwritingandreadingthemessagefields.Theclassiscalled//"Person"andisin......
  • R语言GARCH族模型:正态分布、t、GED分布EGARCH、TGARCH的VaR分析股票指数|附代码数据
    全文链接:http://tecdat.cn/?p=31023最近我们被客户要求撰写关于GARCH族模型的研究报告,包括一些图形和统计输出。如何构建合适的模型以恰当的方法对风险进行测量是当前金融研究领域的一个热门话题 ( 点击文末“阅读原文”获取完整代码数据******** )。VaR方法作为当前业内比较......
  • NLP自然语言处理—主题模型LDA案例:挖掘人民网留言板文本数据|附代码数据
    全文链接:http://tecdat.cn/?p=2155最近我们被客户要求撰写关于NLP自然语言处理的研究报告,包括一些图形和统计输出。随着网民规模的不断扩大,互联网不仅是传统媒体和生活方式的补充,也是民意凸显的地带。领导干部参与网络问政的制度化正在成为一种发展趋势,这种趋势与互联网发展的时......
  • C语言-变量
    变量的作用域作用域(scope)指的是变量生效的范围。C语言的变量作用域主要有两种:文件作用域(filescope)和块作用域(blockscope)。文件作用域(filescope)指的是,在源码文件顶层声明的变量,从声明的位置到文件结束都有效。intx=1;intmain(void){printf("%i\n",x);}上面示例中,变......