数据结构——原来二叉树可以这么学?(4.链式二叉树)

news2024/11/15 6:42:57

前言:

  在前两篇小编讲述了二叉树的顺序结构存储方式,从而讲到了一个特殊的二叉树——堆,通过对于堆的实现来帮助我们对于顺序结构存储方式的实现,下面小编将要讲述二叉树的另一种存储方式——链式结构,链式二叉树来喽,那么废话不多说,代码时间到~

目录:

1.链式二叉树的创建

 1.1.二叉树结点的创建

 1.2.手动创建链式二叉树

2.前中后序遍历

3.链式二叉树其余功能的实现

正文:

1.链式二叉树的创建

1.1.二叉树结点的创建

  本文的标题就已经表明了此时的二叉树我们肯定使用链表实现的,即用链来指各元素之间的逻辑关系。结点的创建方法有很多种,下面小编就先给读者朋友介绍我用的结点创建方法,小编就把这种方法称之为左右子树法(这不是官方的名字,这是小编自己造的),就是在结点的结构体中,我们存有数值,以及左右指针,左右指针分别存放左孩子和右孩子的地址,下面小编直接上代码来给各位展示此结点的结构:

typedef int BTDataType;  

typedef struct  BinaryTreeNode {
	//存数据
	BTDataType data;    //可以理解为数据域
	//左结点(左孩子)
	struct  BinaryTreeNode* left;
	//右节点(右孩子)
	struct  BinaryTreeNode* right;
}BT;

 1.2.手动创建二叉树

  可能很多读者朋友看到这个会很疑惑,为什么链式二叉树需要我们去手动创建,而不是通过函数来进行创建,小编其实在学习的时候也很疑惑,之后老师也做出了解释,我们需要手动创建二叉树的原因是因为二叉树的限制其实不多,它对于结点的插入并没有什么硬性的需求,所以此时我们没必要写函数去实现,之后的二叉搜索树什么的就有很大的要求,那个时候我们就不可以手搓二叉树了,就得通过函数实现了,所以此时我们仅需手动创建二叉树即可,对于二叉树的手动创建是蛮容易的,此时我们需要用到一个我们的老朋友函数:新结点创建的函数,这个函数小编在以前多次写到过,此时我就不在多赘述了,下面先给出创建新结点的函数:

BT* newbuynode(BTDataType x)
{
	BT* pour = (BT*)malloc(sizeof(BT));
	assert(pour);
	pour->data = x;
	pour->left = pour->right = NULL;
	return pour; //别忘记return
}

  之后我们仅需手动去创建多个结点,然后通过左右子树把他们连起来即可,类似我们链表的连法,下面小编直接给出代码:

	BT* node1 = newbuynode(1);
	BT* node2 = newbuynode(2);
	BT* node3 = newbuynode(3);
	BT* node4 = newbuynode(4);
	node1->left = node2;
	node1->right = node3;
	node2->left = node4;   //建立二叉树

  等一会小编讲述下面内容的时候小编会拿上面这个简短的二叉树来进行了解,这里先给各位读者朋友解释一下,下面我们就要进入二叉树的遍历喽~ 

2.前中后序遍历

2.1.前序遍历(先序遍历)

 2.1.1.前序遍历的理论讲解

  可能很多读者朋友对前序遍历这个名字是感到有点陌生的,前序遍历其实就是访问根结点的顺序在左右子树之前,也就是说我们是先访问根结点的数据,然后在取访问根结点的左右孩子,从而我们可以知道前序遍历的遍历顺序是根结点——左子树——右子树(俗称根,左,右),此时我们想要打印二叉树结点的数据就可以用前序遍历的方式进行打印,我们需要先打印根结点的数据,然后在分别打印左孩子,左孩子也得通过前序遍历的方式进行打印,等左孩子打印完之后,我们就可以进行打印右孩子的数据了,右孩子同样也需要进行前序遍历的方式进行打印,打印完之后,我们就可以实现出一个二叉树的前序遍历,这便就是理论部分,下面小编将会讲述代码部分(准备开始递归风暴吧~)

 2.1.2.前序遍历的代码实现

  在经过理论部分的解释,可能有读者朋友想到了我们在C语言阶段学习的一个大头——函数的递归,没错,前序遍历就是依靠函数的递归进行实现的,函数递归的特点就是把一个大的事情划分wield好几个小事情进行解决,直到子问题不能在进行拆分了,递归就结束了,这正好就符合了前序遍历的打印,当然,我们一定要在函数内部设置一个结束递归的条件,不然就会一直递归下去从而导致栈溢出,对于前序遍历函数的实现其实是很容易的,但是,理解下来可能就不会这么容易了,下面小编先来说一下这个题目的代码怎么写。

  首先,我们肯定是要先设置递归条件的,这个条件的设置也很容易,当我们递归到空结点的时候,此时就无法在往下递归了,此时我们直接return结束这个函数栈帧就好,之后我们根据根,左,右的原则,我们先打印此时结点的值,然后在递归左孩子,右孩子即可,此时我们就写完了这个函数,是不是感觉写起来很简单?下面小编先给出代码,然后在通过这个代码给各位读者朋友更详细的解释:

//前序遍历二叉树(根左右)
void PreOrder(BT* root)
{
	if (root == NULL)
	{
		return;
	}
	printf("%d ", root->data);
	PreOrder(root -> left);
	PreOrder(root -> right);	
}
 2.1.3.前序遍历的代码解释

  对于上述的代码,对于对函数递归还感到很陌生的读者朋友可能会有疑惑,小编此时通过图文的方式来帮助各位读者朋友去更好的了解:

  刚开始,我们先借助我们刚刚手动创建的二叉树先生成一颗二叉树:

   之后我们就要开始进行递归了,看代码,首先我们先要判断此时结点是否为空,很明显根结点此时并不为为空,我们直接打印它的数值,也就是1,然后我们开始左子树的递归:

  还是先判断一下左孩子是否为空的结点,很明显不是,然后我们打印完数值2以后,继续往下去进行左孩子的递归:

  之后我们先判空,不是后打印数值4,此时我们仍需完成左孩子的递归,此时我们可以知道这时的左孩子已经是空了,然后我们向右递归右孩子,也是空的,此时我们就结束了4这个函数的栈帧,我们开始往上进行传递:

  之后我们再去判断2这个结点的右孩子是否为空,很明显右孩子的确是空的,此时直接返回就好了,然后2这个函数栈帧也结束了,我们继续往上走:

  此时我们回到了根结点,此时我们在进行判断右孩子是否为空,很明显不为空,此时我们打印完3之后,继续左孩子右孩子递归,很明显左孩子和右孩子都是空的,所以我们仅需返回就好,此时3这个函数栈帧结束,返回后也代表着最初的函数成功递归完成,函数功能完成,函数栈帧销毁:

   此时这个函数就已经完成,小编在前序遍历详细讲一讲递归的过程,之后的中序遍历和后续遍历小编就可能一笔带过了,因为其实原理都是一样的,下面给出这个代码的运行图:

2.2.中序遍历

 2.2.1.中序遍历的理论部分

  中序遍历通过它的名字我们就可以知道,根结点是在左子树和右子树之间进行访问的,所以从这我们就可以知道,前中后序遍历,其实是通过访问根结点的顺序来进行区分的,所以中序遍历的访问顺序是:左子树 ——根结点——右子树(俗称左根右),我们先访问左子树的值,然后在访问根结点的值,最后访问右子树的值,这样以来我们就实现了中序遍历,下面进入代码讲解。

 2.2.2.中序遍历的代码实现

  此时我们肯定也是用到了函数递归,第一步,我们依旧是确定递归的条件,此时递归的条件和前序遍历是一样的,我们仍需判断此时递归传过来的结点是不是空的结点,如果是空的结点,我们直接返回结束函数就好,之后我们打印的顺序自然是不能放在开头了,我们先递归左子树,然后打印结点的数据,之后在递归右子树,此时我们的函数也是写完了,与我们写前序遍历的函数是类似的,只不过此时打印的位置往后走了一下而已,下面小编给出代码图,然后在简单的各位读者朋友讲述一下原理:

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	InOrder(root -> left);
	printf("%d ", root->date);
	InOrder(root->right);
}
 2.2.3.中序遍历的代码讲解(极简版)

  由于小编在前序遍历的时候就已经详细的解释了我们如何借助递归来实现前序遍历的,此时我们中序遍历不能说是和前序遍历一样,只能说差距不大,小编通过给出几行文字来帮助各位读者朋友进行了解。 先给出前面我们创建的二叉树的图,防止读者朋友忘记:

  首先,我们还是按照上面手动创建的二叉树为例,此时我们先判断结点是否为空,很明显不是,所以我们进行左子树的递归,此时到了2这个结点,然后我们继续判断是否为空,很明显不是,然后我们继续递归,此时到了4,这个结点也不是空的,我们需要继续递归,此时递归下去的结点就是空的,所以我们直接返回就好,然后我们就可以打印4这个数据了,然后我们往右进行递归,很明显右孩子也是空的,此时我们直接返回就好,4这个函数栈帧就结束了,以此递推,我们就完成了中序遍历,下面小编给出中序遍历的运行结果:

2.3.后序遍历

 2.3.1.后序遍历的理论讲解

  后序遍历通过它的名字我们便可以知道此时我们根结点肯定是放在最后面了,访问根结点的操作是放在了左右子树之后了,所以后序遍历的遍历顺序是:左子树——右子树——根结点(俗称左右根),我们先访问左子树的值,然后访问右子树的值,最后在访问根结点的值,在知道了前序遍历和中序遍历以后,后序遍历简直就是so easy,下面进行代码实现部分。

 2.3.2.后序遍历的代码实现

  后序遍历的代码实现也很轻松,首先涉及到递归,我们肯定要设置递归结束的条件,还是判断此时的结点是否为空,如果为空的话,我们直接return结束此函数即可,之后我们先往左子树进行递归,再往右子树进行递归,最后我们在打印数据即可,此时我们就完成了函数的实现,下面小编先给出代码,然后在更简短的讲述一下代码原理:

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	PostOrder(root -> left);
	PostOrder(root -> right);
	printf("%d ", root->date);
}
 2.3.3.后序遍历的代码讲解(极简版)

  在经过前面递归代码的摧残以后,小编相信读者朋友可能已经大致明白了我们如何通过递归实现遍历,但是可能有些读者朋友还会感到一些模糊,所以小编就在简单讲述一下原理,老规矩,先放上前面小编创建的二叉树:

   首先我们还是先判断结点是否为空,很明显此时不为空,我们先往左进行递归到2,然后经过同样的处理,继续往左边递归到4,此时4的左右子树都是空的,所以此时我们打印完4以后就可以往上进行回归操作,此时回到2,2的右子树是空的,所以我们打印完2以后继续回归,回归到1以后,我们就可以进行向右递归判断,递归到3以后,可知3的左右孩子都是空的,所以打印完3以后就回归到1,此时1的左右子树递归完成,打印完1以后函数栈帧结束,此时函数功能实现,下面小编给出此时后序遍历的运行图:

  此时我们就讲述完了前中后序遍历,下面我们就要开始进行二叉树其余功能的实现了,大家做好车,准备出发,去迎接递归风暴喽~

3.二叉树其余功能的实现

3.1.二叉树结点个数(int BinaryTreeSize(BTNode* root))

 3.1.1.功能解读

  此功能是想让我们去求结点的个数,此时我们需要分别统计左子树和右子树的结点个数,之后再加上根结点1即可,这个功能说起来是很好实现的,但是当我们想要通过代码去实现的时候,可能会有一些困难,可能一些读者朋友认为可以设置一个全局变量,然后我们通过不断的递归,每递归一次这个变量就加1,来去实现这个功能就好了,小编当初也这么想过,经过打印后也确实做到了,不过这个指定是错误的,因为此时这个全局变量的值已经发生改变,当我们又再次想计算二叉树结点个数时,此时就相当于二叉树结点个数+二叉树结点个数,所以这个方法直接out掉,不过,这个方法有一半是正确的,就是这个题目的实现,我们还是需要用到递归来做,下面小编就给出此代码正确的代码实现。

 3.1.2.代码实现

  此函数涉及到了递归的知识,所以我们首先需要去写递归结束的条件,条件和上面的遍历的条件是一样的,如果此时的结点是空的话,我们直接返回0就好,之后我们仅需直接返回1(根结点)+递归左子树+递归右子树就可以实现出这个功能,此时这个函数就实现完成了,可能很多读者朋友会懵,这个题目就这么完成了?不要急,小编先给出代码,之后在给上代码解释:

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return  BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
 3.1.3.代码讲解

  下面小编会继续用上面创建的二叉树作为例子,来帮助各位读者朋友去明白这个代码的意义:

   首先我们先去判断1这个结点是否为空,指定不是空的,之后我们便开始向左进行递归,然后继续判断是否为空,可看出2不为空,之后继续向左递归,此时4这个结点不是空,然后向左递归,发现为空,返回0,在向右结点递归,发现也为空,返回0,之后我们返回0+0+1也就是1,此时这个函数栈帧就结束了,销毁后开始往上回归:

  回归到2这个结点以后,我们在递归2的右子树,发现右子树为空,此时返回的是0,然后2这个结点的函数返回1(左子树)+ 0 + 1(本身)也就是2后函数栈帧销毁,继续往上回归:

   此时1这个根结点的左子树递归完毕,开始递归右子树,此时3这个结点不是空,不过它的左右子树都是空,此时我们返回0即可,之后3这个结点左右子树递归完毕,返回0+0+1也就是1之后,三这个子树递归完毕,此时1的右子树也递归完毕,同样的返回2 (左子树)+ 1(右子树) +1(本身)也就是4即可,此时这个函数栈帧也结束了,整个函数也已经结束,此时我们可以知道此二叉树的结点个数是4:

  下面小编给出这个功能的运行图,然后我们继续下一个功能的实现:

3.2.叶子结点个数(int BinaryTreeLeafSize(BTNode* root))

 3.2.1.功能解读

  叶子结点的概念可能很多读者朋友都已经忘记了,小编在这里先简单的带着各位回忆一下,叶子结点就是一个结点,没有左右孩子,此时这个结点被称之为叶子结点,此时这个题目想让我们去求叶子结点的个数,此时我们可以依靠它这个性质来去求解叶子结点,通过分别求左右子树的叶子结点从而做到求到整个树的叶子结点,下面小编在代码部分会告诉大家如何实现这个代码。

 3.2.2.代码实现

  此时我们想要求到左右子树的叶子结点,递归是不可或缺的,此时我们首先要知道递归的条件,此时递归的条件和上面是一样的,当结点为空时直接返回0即可,此时与上面不同的是,我们需要判断一个结点,如果它的左右子树都是空的话,说明这个是也子结点,直接返回1,最后我们返回左子树+右子树的值即可,此时这个函数就写完了,感觉写完了上面那个函数以后,这个函数写起来可以说是很轻松,下面小编给出代码:

int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
 3.2.3.代码讲解

  为了防止各位读者朋友不太理解上面的代码,下面小编继续通过上面的例子来帮助各位理解这个代码 :

  此时1这个结点不是叶子结点,往左递归到2,2这个结点也不是叶子结点,继续往左递归到4,此时4这个结点的左右子树均为空,说明是叶子结点,返回1,然后回归到上一层:

  之后我们递归2的右子树,可以知道2的右子树为空,所以返回0,此时2的函数栈帧已经结束,继续回归到1,然后递归1的右子树,可知3这个结点的左右子树都是空,所以是叶子结点,直接返回1,3的函数栈帧销毁,回归到1,然后1这个结点返回1+1后也完成了函数栈帧销毁,之后整个函数功能完成,我们也知道了叶子结点是2个:

   下面小编给出运行图:

3.3.二叉树第k层结点的个数(int BinaryTreeLevelKSize(BTNode* root, int k)) 

 3.3.1.功能解读

  此时我们已经来到了函数的第三个功能,这个功能通过名字我们就可以大致知道什么意思了,其实就是想让我们寻找某一层的结点个数,就比如我们想要寻找第二层的结点,通过上面的二叉树为例,我们可以知道结点个数是2个,所以这个功能就要考察我们去实现二叉树第k层结点的能力,这里我们也得分别求出左右子树第k层结点的个数,然后相加即可,下面小编就要用代码去实现它喽~

 3.3.2.代码实现

  通过功能解读,各位可以知道我们仍需要知道左右子树,所以我们还得用到本文最常出现的它——递归来实现,所以我们还需要写递归的判断条件,自然还是如果此结点为空,我们直接返回0即可,下面我们思考的方向就是我们如何去找到第k层的结点,其实也很容易,我们每次递归左右子树的时候,k都要去减1,当k==1的时候,此时便到了我们想要层数的结点的个数,我们直接返回1即可,之后我们仅需把左右子树递归的结果相加,然后返回即可,这便是这个题目的解题流程,可能有些读者朋友还会有些许疑惑,不要着急,小编在放完代码后还是会对代码进行讲解的,下面小编先给出代码:

int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
 3.3.3.代码讲解

  此时小编还是通过上面的二叉树作为例子,此时我们就寻找二叉树第二层的结点个数:

  此时我们先去判断1这个结点是否为空,很显然不为空,此时k也不等于1,此时我们先递归左子树,并且此时k-1到1,之后到了2这个结点,这个结点的k值正好为1,说明此时就是我们想要找的那一层,我们直接返回1:

  之后我们去递归1的右子树,此时3这个结点的层数也符合我们想要寻找的层数,我们直接返回1,之后1这个结点的左右子树都已递归完毕,我们返回1+1后,此函数栈帧销毁,函数功能实现完毕:

  此时函数功能实现完毕,下面我们来测试一下代码是否可以正常运行:

3.4.二叉树的高度/深度(int BinaryTreeDepth(BTNode* root))

 3.4.1.功能解读

  这个功能其实读者朋友一读就可以明白,这个还是比较直观的,我们此时需要去求二叉树的高度,也就是说一共有几层二叉树,此时我们仍需知道左右子树,通过左右子树的比较大小,最大的那个就代表着二叉树有几层,下面我们开始进行代码实现。

 3.4.2.代码实现

  此时我们还是需要通过递归来实现这个题目,递归条件和上面一样,我们仅需判断结点是否为空,如果为空我们之间返回0即可,之后我们需要通过保存左右子树递归的值,然后我们通过判断左右子树递归的大小来寻找最大的值,之后我们让此值加一,至于我们为何去找最大的值,因为二叉树的高度取决于结点的位置而不是结点的个数,如果左子树结点多但是矮,右子树结点少但是高,那么二叉树的高度自然是按照右结点的高度来,此时我们就实现了这个函数,由于小编在前面讲述了不少递归的知识,小编来考验一下大家对于此代码的理解能力,对于此函数的解释各位先不看,看自己是否可以理解这个代码,下面给上代码:

int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int left = BinaryTreeDepth(root->left);
	int right = BinaryTreeDepth(root->right);
	return left > right ? left + 1 : right + 1;
}
 3.4.3.代码讲解

  此时我们还是按照上面的二叉树进行讲解,图小编就不复制过来开了,各位读者朋友可以翻到上面看一看,此时我们首先要判断1这个结点是否为空,肯定不是空,然后我们向左疯狂递归,直到递归到4的左结点,此时可以知道结点为空,我们返回0即可,之后我们在递归4的右子树,也为空,此时我们就可以判断left和right的大小,此时左等于右,所以right+1也就是1,此时4的函数栈帧结束,回归到2这个结点:

  之后我们递归2的右子树,此时为空返回0,然后比较left和right的大小,很显然我们返回left+1也就是2即可,之后我们就要判断1这个结点的右子树,然后在通过同样的方法得出right为1,此时left为2,所以我们返回3,函数栈帧结束,函数功能实现完毕,我们得出此时的层数是3:

  老规矩,我们还是检测一下此时代码的正确性:

3.5.二叉树查找值为x的结点(BTNode* BinaryTreeFind(BTNode* root, BTDateType x))

 3.5.1.功能解读

  这个功能也很好描述,这个功能名就说明了此时我们需要求二叉树中数值为x的结点,如果找到我们就返回 该结点,如果没有找到我们直接传空就好,此时我们仍需需要找到左右子树,来寻找到我们想要的数值为x的结点,如果找到了我们直接返回结点即可,下面进入代码实现部分。

 3.5.2.代码实现

  此时我们仍需用到递归的知识来帮助我们找左右子树,此时我们还是要写递归的条件,与上面一样,当此时的结点为空时,我们返回NULL即可,之后还需要判断此时结点的值是否是x,如果是x的话,我们直接返回结点即可,之后我们递归左子树,当此时左子树递归到的结点的数据存放着x时,我们直接返回结点即可,如果还是没有,我们向右递归右子树,如果右子树递归到的结点的数据存放着x,我们还是返回结点即可,如果此时左右子树递归完毕我们还没有找到想要结点的时候,此时我们直接返回NULL即可,证明该二叉树中是没有我们想要找到的结点的,此时这个函数功能实现完毕,下面小编给出代码:

BTNode* BinaryTreeFind(BTNode* root, BTDateType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->date == x)
	{
		return root;
	}
	BTNode* left = BinaryTreeFind(root->left, x);
	if (left->date == x)
	{
		return left;
	}


	BTNode* right = BinaryTreeFind(root->right, x);
	if (right->date == x)
	{
		return right;
	}

	return NULL;
}
 3.5.3.代码讲解

  为了帮助各位读者朋友去更好的掌握这个函数的知识点,小编还是简单的说一下此时的代码是如何来实现的,下面我们以上面创建的二叉树为例,然后x是2来作为例子进行讲解,此时我们先判断1这个结点是否为空,指定不是空的,我们在判断数据是否是2,也不是,开始往左子树进行递归,递归到2这个结点,这个不仅不是空的,而且此时的结点存放的数据正好是我们想要找的,此时我们直接返回该结点:

  之后我们在进行完左子树递归的时候,此时我们触碰到下一个if语句,此时我们直接返回2这个结点即可,此刻1这个函数栈帧销毁,函数功能实现完毕,下面小编给出最后的流程图以及小编检测此时的代码是否可以正常运行:

 3.6.二叉树的销毁(void BinaryTreeDestory(BTNode** root)) 

 3.6.1.功能解读

  此时我们终于来到了最后一个功能的讲解,在我们实现完一系列的功能,此时二叉树的任务就完成辣,下面我们就要进行销毁结点的部分了,因为此时我们需要销毁根结点,想要彻底消除根结点,我们就需要去传根结点地址的地址,也就是用二级指针来进行接受,下面开始进入我们的代码实现部分:

 3.6.2.代码实现

  首先,因为我们还需要把左右子树进行销毁,所以我们还是要有递归的知识,递归条件还是如果此时结点为空,我们直接return即可,之后的操作其实就和我们上面写的后序遍历一样,我们需要先销毁左右子树的结点,最后我们在销毁根结点,所以我们先向左子树递归,再往右子树递归,之后我们直接把该结点free掉,然后让它指向空即可,养成好的代码习惯。减少野指针的出现,从你我做起,此时这个函数的功能就实现完毕了,下面小编展示一下代码:

void BinaryTreeDestory(BTNode** root)
{
	if (*root == NULL)
	{
		return;
	}
	BinaryTreeDestory(&((*root)-> left ));
	BinaryTreeDestory(&((*root)-> right ));
	free(*root);
	*root = NULL;
}
 3.6.3. 代码讲解(简单说说了)

  因为此时的代码和上面的后续遍历除了最后部分有差异其他其实都是一样的,所以我相信各位读者朋友可以很快了解这个代码,当然忘了的话也可以往前面看,小编就从这里简单的说一下这个代码的实现流程,首先我们还是看此时1这个结点是否为空,肯定不为空,然后想左递归,一直递归到4这个结点的时候,开始递归4的左右子树,很明显都是空,然后我们把4销毁后回归,然后以此销毁2,3,1后,此时我们就把整个二叉树进行销毁了,我们就正式的完成了这个代码的功能,各位读者朋友写完后一定要测试自己代码的正确性,小编刚才在调试的时候,在调用函数的时候,实现忘记&node1,而是直接node1,导致我找错误找了很久,所以读者朋友们一定好好的去写每一行代码,减少自己找错的机会。

  此时小编已经写完了链式二叉树一些比较基本的功能,下面小编先给出链式二叉树所有的代码供各位读者朋友参考~

4.代码展示

4.1.Tree.h

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


//今天来复习一下链式二叉树的写法,得加快复习的脚步了~

typedef int BTDateType;

typedef struct BinaryTreeNode
{
	BTDateType date;
	struct BinaryTreeNode* left;//左孩子
	struct BinaryTreeNode* right;//右孩子
}BTNode;

//前序遍历
void PreOrder(BTNode* root);

//中序遍历
void InOrder(BTNode* root);

//后序遍历
void PostOrder(BTNode* root);

//二叉树结点个数
int BinaryTreeSize(BTNode* root);

//叶子结点的个数
int BinaryTreeLeafSize(BTNode* root);

//二叉树第k层结点的个数
int BinaryTreeLevelKSize(BTNode* root, int k);

//二叉树的高度/深度
int BinaryTreeDepth(BTNode* root);

//二叉树查找值位x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDateType x);

//二叉树销毁
void BinaryTreeDestory(BTNode** root);

4.2.Tree.c

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

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


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


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



int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}


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


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


BTNode* BinaryTreeFind(BTNode* root, BTDateType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->date == x)
	{
		return root;
	}
	BTNode* left = BinaryTreeFind(root->left, x);
	if (left->date == x)
	{
		return left;
	}


	BTNode* right = BinaryTreeFind(root->right, x);
	if (right->date == x)
	{
		return right;
	}

	return NULL;
}


void BinaryTreeDestory(BTNode** root)
{
	if ((*root) == NULL)
	{
		return;
	}
	BinaryTreeDestory(&((*root)-> left ));
	BinaryTreeDestory(&((*root)-> right ));
	free(*root);
	*root = NULL;
}

5.总结

  终于讲完了链式二叉树,这是小编第二次写的万字博客,本来一下午就可以写完的,我硬生生的又拖了一天,小编这篇博客讲述的我自认为算是比较详细的了,当然小编有的还是偷了懒少写了点,不过无伤大雅,此时链式二叉树其实小编还有两个功能没有实现,那两个功能涉及到了队列的知识,小编先简单透露一下,如果本文有一些错误,欢迎各位读者朋友在评论区指出,小编看到消息后会及时更正,那么,我们下一篇博客见啦~

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

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

相关文章

专业学习|GERT网络概览(学习资源、原理介绍、变体介绍)

一、GERT 网络概览 GERT(Graphical Evaluation Review Technique&#xff0c;图示评审技术)是一种结合流线图理论(Flow Graphical Theory)、矩母函数(Moment Generating Function)、计划评审技术(Program Evaluation Review Technique)解决随机网络问题的方法&#xff0c;描述各…

2024年CAD图纸加密软件|加密图纸软件推荐:10款高效CAD加密软件

在当今数字化时代&#xff0c;CAD图纸已成为工程设计、建筑规划、机械制造等领域不可或缺的重要文件。然而&#xff0c;随着数据泄露和信息安全问题的日益严重&#xff0c;保护CAD图纸的安全性变得尤为重要。为了确保设计数据的安全&#xff0c;使用高效的CAD图纸加密软件成为了…

CMAT:提升小型语言模型的多智能体协作调优框架

人工智能咨询培训老师叶梓 转载标明出处 大模型&#xff08;LLMs&#xff09;已经成为自然语言处理&#xff08;NLP&#xff09;的基石。然而&#xff0c;这些模型的有效运行仍然在很大程度上依赖于人为输入来准确引导对话流程。为了解决这一问题&#xff0c;来自华东交通大学…

comfyui中报错 Cmd(‘git‘) failed due to: exit code(128) 如何解决

&#x1f388;背景 comfyui今天在安装插件的过程中&#xff0c;发现有个插件第一次安装失败后&#xff0c;再次安装就开始报错了&#xff0c;提示&#xff1a; ComfyUI-Inpaint-CropAndStitch install failed: Bad Request 截图如下&#xff1a; 看下后台的报错&#xff1a; …

Html css水平居中+垂直居中+水平垂直居中的方法总结

1. Html css水平居中垂直居中水平垂直居中的方法总结 1.1. Html 元素 1.1.1.元素宽高特点 &#xff08;1&#xff09;块级元素&#xff08;如div&#xff09;&#xff1a;独占一行&#xff0c;不和其他元素在一起&#xff0c;可以设置宽和高&#xff1b;当没设置宽和高时&am…

宝塔面板FTP连接时“服务器发回了不可路由的地址。使用服务器地址代替。”

参考 https://blog.csdn.net/neizhiwang/article/details/106628899 错误描述 我得服务器是腾讯&#xff0c;然后使用宝塔建了个HTML网站&#xff0c;寻思用ftp上传&#xff0c;结果报错&#xff1a; 状态: 连接建立&#xff0c;等待欢迎消息... 状态: 初始化 TLS 中... 状…

go多线程

1、简单使用&#xff08;这个执行完成&#xff0c;如果进程执行比较久&#xff0c;这里不会等待它们结束&#xff09; package mainimport "time"func main() {go func() {println("Hello, World!")}()time.Sleep(1 * time.Second) }2、wg.Add(数量)使用&…

nginx服务介绍

nginx 安装使用配置静态web服务器 Nginx是一个高性能的Web服务器和反向代理服务器&#xff0c;它最初是为了处理大量并发连接而设计的。Nginx还可以用作负载均衡器、邮件代理服务器和HTTP缓存。它以其轻量级、稳定性和高吞吐量而闻名&#xff0c;广泛用于大型网站和应用中 Ngin…

2024ICPC网络赛1: C. Permutation Counting 4

题意&#xff1a; 给定 n n n个区间 [ L i , R i ] [L_i,R_i] [Li​,Ri​]设集合 A { { p i } ∣ p i 为排列&#xff0c; L i < p i < R i } A\{ \{ p_i\} | p_i为排列&#xff0c;Li<p_i<R_i\} A{{pi​}∣pi​为排列&#xff0c;Li<pi​<Ri​}&#xff…

图解Redis 01 | 初识Redis

什么是 Redis&#xff1f; Redis 是一种基于内存的数据库&#xff0c;所有的数据读写操作都在内存中完成&#xff0c;因此读写速度非常快。它被广泛应用于缓存、消息队列、分布式锁等场景。 Redis 提供了多种数据类型来支持不同的业务需求&#xff0c;如 String、Hash、List、…

Vscode 中新手小白使用 Open With Live Server 的坑

背景 最近在家学习尝试前端项目打包的一些事项&#xff0c;既然是打包&#xff0c;那么肯定就会涉及到对打包后文件的访问&#xff0c;以直观的查看打包后的效果 那么肯定就会使用到 Vscode 中 Open With LIve Server 这个功能了&#xff0c;首先这个是一个叫 Live Server 的…

18、Python如何读写csv文件

先简单介绍一下 csv 格式的文件是什么意思。先看一下百度百科怎么说的。 逗号分隔值&#xff08;Comma-Separated Values&#xff0c;CSV&#xff0c;有时也称为字符分隔值&#xff0c;因为分隔字符也可以不是逗号&#xff09;&#xff0c;其文件以纯文本形式存储表格数据&…

企业绿色信贷水平研究:全国与分省份数据分析(2005-2021年)

企业绿色信贷水平指的是企业在信贷活动中&#xff0c;根据环保和产业政策&#xff0c;对环保企业进行扶持&#xff0c;对污染企业进行资金遏制的能力。 2005-2021年 上市企业绿色信贷水平全国、分省份数据&#xff08;原始数据和计算方法&#xff09;https://download.csdn.ne…

鸿蒙读书笔记2:《鸿蒙操作系统设计原理与架构》

2. OS基础平台部件化 &#xff08;1&#xff09;内核层 内核层包括内核部件和HDF驱动框架部件。当前已提供LiteOS-M、 LiteOS-A、Linux和UniProton这4种内核部件&#xff0c;未来还可增加更多类 型的内核部件。LiteOS、Linux内核部件可以按需部署在不同设备之 上&#xff0c;内…

腾讯百度阿里华为常见算法面试题TOP100(3):链表、栈、特殊技巧

之前总结过字节跳动TOP50算法面试题&#xff1a; 字节跳动常见算法面试题top50整理_沉迷单车的追风少年-CSDN博客_字节算法面试题 链表 160.相交链表 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) …

29 线性表 · 队列

目录 一、概念与结构 &#xff08;一&#xff09;概念 1、队列 2、入队列 3、出队列 &#xff08;二&#xff09;底层结构 二、队列的实现 三、队列的算法题 &#xff08;一&#xff09;用队列实现栈 &#xff08;二&#xff09;用栈实现队列 &#xff08;三&#xf…

基于AG32 的USB转以太网方案

如何通过USB转以太网标准模块&#xff1f; AG32支持USB FSOTG和以太网MAC&#xff0c;并且提供了标准例程&#xff0c;包括网络Lwip和USB的开发例程&#xff0c;上层应用调tinyUSB的接口即可。 以下是AG32VF407VG的引脚定义&#xff0c;支持USB外设。 LQFP-100Pin nameAG32VFx…

简单了解Maven与安装

Maven 1.Maven 简介 Maven 是 Apache 软件基金会&#xff08;国外组织&#xff0c;专门维护开源项目&#xff09;的一个开源项目, 是一个优秀的项目构建工具, 它用来帮助开发者管理项目中的 jar, 以及 jar 之间的依赖关系(在A.jar文件中用到了B.jar)、 完成项目的编译&am…

圆环加载效果

效果预览 代码实现 from PyQt5.QtCore import QSize, pyqtProperty, QTimer, Qt, QThread, pyqtSignal from PyQt5.QtGui import QColor, QPainter from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QPushButton, QVBoxLayout, QLabel, QGridLayoutclass Cir…

Rust使用Actix-web和SeaORM库开发WebAPI通过Swagger UI查看接口文档

本文将介绍Rust语言使用Actix-web和SeaORM库&#xff0c;数据库使用PostgreSQL&#xff0c;开发增删改查项目&#xff0c;同时可以通过Swagger UI查看接口文档和查看标准Rust文档 开始项目 首先创建新项目&#xff0c;名称为rusty_crab_api cargo new rusty_crab_apiCargo.t…