数据结构——二叉树链式结构(递归实现)

目录

二叉树的遍历

概念介绍

前序遍历递归实现  PreOrder

中序遍历递归实现  InOrder

后序遍历递归实现  PostOrder

二叉树节点总个数  TreeSize

二叉树的高度  TreeHight

二叉树第K层的节点个数  TreeKLevel

二叉树的节点查找  TreeFind

二叉树的叶子节点个数  BinaryTreeLeafSize

手撕一棵树,检测代码

总代码 & gitee

结语


二叉树的遍历

概念介绍

二叉树的遍历分为

  • 前序   ——    根      左子树      右子树
  • 中序   ——    左子树      根      右子树
  • 后序   ——    右子树      左子树      根
  • 层序   ——    一层一层往下遍历

我们在看二叉树时应该这样看:

如下这是一棵二叉树

最上面的节点是根,下面是两大棵树,左节点以下的是左子树,右边的则是右子树

接下来假设我们先遍历左,那么我们就该这么看:

这一个整个左子树里面接着分成——根、左子树、右子树

再往下分:

根、左子树、右子树

如果还要再往下分的话,左子树和右子树都为空,如果遍历二叉树的话,到这里就该结束了

前序遍历递归实现  PreOrder

通常在遍历二叉树的时候,都会采用递归遍历

这里我们以  前序遍历  为例

前序遍历,就是遍历 根 -> 左子树 -> 右子树 ,然后现在我们需要遍历一下如下这棵树:

我们先遍历根,现在根是 1,就先找到 1。然后左子树,但是左子树不是单一一个节点,所以我们需要将左子树当成一棵完整的树,再来遍历一遍 根、左子树、右子树:

先找根,所以在 1 之后,第二个找到的是 2

沿着这个思路继续,2 之后是 4,4 的左右为空,我们就用 N 来代替 ——>  以 4 为根的树找到的就是 4、N、N。目前找到的是  1、2、4、N、N

接下来递归回去:以 4 为根的左子树找完了,就意味着 2 的左子树找完了,接下来就是 2 的右子树,为空,记为 N

2 的找完了。就意味着 1 的左子树找完了,接下来就是找 1 的右子树,但右子树也不止一个节点,所以还是按上述操作,再次执行一遍:

如上,因为还是前序遍历,所以我们还是按  根、左子树、右子树 的顺序,先找到 3,随后是 5、N、N、6、N、N

综上,这一整棵树前序遍历的结果就是:

1、2、4、N、N、N、3、5、N、N、6、N、N

但如果要递归实现的话,我们就需要考虑一下:递归条件是什么?

递归,简单点说就是将大问题拆分成小问题,小问题再拆分成小问题,当不满足某个条件或满足某个条件时,递归就要往回走了

这里我们的条件是:当遍历到空节点,就是最下面了的时候,停止遍历

然后打印当前节点(根)所带的值(这里我们以前序为例),然后再调用一次函数,将根节点的左孩子节点放入函数中,接着是右孩子节点

代码如下:

//前序遍历
void PreOrder(TN* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", root->val);
	PreOrder(root->left);
	PreOrder(root->right);
}

我们来分析一下这个递归展开图

我们现在遍历这棵树:

如上是这一段的递归示意图,最终的结果应该是:

1、2、3、N、N、N、4、5、7、N、N、8、N、N、6、N、N

运行一下程序看结果:

中序遍历递归实现  InOrder

中序遍历就是遍历  左子树、根、右子树

如果要中序遍历这棵树的话,那么结果就是:N、4、N、2、N、1、N、5、N、3、N、6、N

这里和前序最大的区别就是,先根还是先左子树,所以我们只需要将根的打印和寻找左子树的代码换个顺序,就实现了中序遍历

代码如下:

//中序遍历
void InOrder(TN* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	PreOrder(root->left);
	printf("%d ", root->val);
	PreOrder(root->right);
}

后序遍历递归实现  PostOrder

后序遍历的逻辑和上述并无太大差别,这里就直接给代码了:

//后序遍历
void PostOrder(TN* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	PreOrder(root->left);
	PreOrder(root->right);
	printf("%d ", root->val);
}

二叉树节点总个数  TreeSize

我们如果要计算总个数的话,就相当于是 左子树内的所有节点个数 + 右子树内的所有节点 + 1(当前节点)

当我们当前节点为空时,就返回 0

代码如下:

//节点个数
int TreeSize(TN* root)
{
	return root == NULL ? 0 : 
	TreeSize(root->left) + TreeSize(root->right) + 1;
    //上述代码是放一行的,这里放两行只是因为太长了,并无错误
}

我们来递归展开一下该代码:

节点 1 的左边

节点 1 的右边

综上,我们得出的结果就是 6,答案正确

二叉树的高度  TreeHight

二叉树的高度,实际上就是这个二叉树最大有几层

如上这棵二叉树的高度就是 3

这棵二叉树的高度就是 4

那我们该怎么找到这个高度呢?

试想一下:一个学校的校长想了解两个专业里面哪个学生最高,他应该问一问两个院长对吧,院长会去问主任,主任问辅导员,辅导员问班长

班长对比完之后上报,辅导员、主任和院长也是,校长比了一下两个院长给上来的数据之后,对比了一下,得出了哪个学生最高

如上,我们如果要知道二叉树的高度,我们是不是应该比对一下:左子树和右子树哪边更高?

左子树又分下去,右子树也是,不断将大问题转换成小问题,最后结束的条件就是:找到空了,返回 0

如果不返回 0 的话(中间节点),我们就使用三目操作符,得出左右子树中高的那一个 return 回去

由于我们这里要返回整数(int),所以我们在递归使用函数的时候,需要拿一个整形变量来接收,最后整形变量来参与三目操作符的比较

代码如下:

//树的高度
int TreeHight(TN* root)
{
	if (root == NULL)return 0;
	int leftnum = TreeHight(root->left);
	int rightnum = TreeHight(root->right);
	return leftnum > rightnum ? leftnum + 1 : rightnum + 1;
}

我们来分析一下这个代码的递归展开图(以下图的二叉树为例)

二叉树第K层的节点个数  TreeKLevel

如果我们要知道第K层有多少个节点的话,我们就需要有一个变量能控制递归,即:当到了第K层的时候,递归就应该结束并返回 1 了

另外,如果在遍历的途中,碰到节点为空的情况,就直接 return 0

要递归,我们需要将大问题分解成一个个的小问题:

假设我们的问题是:第 3 层的节点有多少个

那么对第一层来说,第三层是第下下层

对第二层来说,第三层是下一层

对第三层来说,就是这一层

所以我们可以在函数递归的时候,将 K-1 传过去,而当 K 等于 1 时,就代表是这一层节点,return 1 即可

代码如下:

//第K层的节点数
int TreeKLevel(TN* root, int k)
{
	if (root == NULL)return 0;
	if (k == 1)return 1;
	return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}

我们来递归展开一下(遇到空的情况就直接简写了):

二叉树的节点查找  TreeFind

我们如果要查找一个节点的话,比如我要找数节点内放着 3 的节点,如果找到了,那我就返回这个节点的地址,如果找到空指针了(找到空指针就意味着前面的节点都找过了,没有符合条件的节点),就返回 NULL

但是我们的递归是有返回值的,所以我们递归的时候需要有变量接收

这时,接收的变量只会有两个结果,要么是找到了,返回的是那个节点的指针;要么是没找到,返回的是 NULL

所以我们在判断完之后,还是需要将值传回去的,如果传过来的值不为空,就 return 回去,如果为空,就 return NULL

代码如下:

TN* TreeFind(TN* root, int x)
{
	if (root == NULL)return NULL;
	if (root->val == x)return root;

	TN* NodeLeft = TreeFind(root->left, x);
	if (NodeLeft)return NodeLeft;

	TN* NodeRight = TreeFind(root->right, x);
	if (NodeRight)return NodeRight;

	return NULL;
}

假如我们现在要在这样一棵树里面找到 4 这个节点,我们来递归展开分析一下:

二叉树的叶子节点个数  BinaryTreeLeafSize

这里的主要思想是:如果当前节点为空,返回 0

如果该节点的左子树和右子树同时为空,则返回 1

由于这个函数需要返回数据,所以我们需要拿变量来接收,最后返回的是两个变量之和

递归就是将大问题转换成小问题:我们的叶子节点,就是左子树的叶子节点+右子树的叶子节点

代码如下:

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

	int a1 = BinaryTreeLeafSize(root->left);
	int a2 = BinaryTreeLeafSize(root->right);

	return a1 + a2;
}

该函数就不画递归展开图了,前面的递归展开图理解了的话,再来看这个会有不一样的感受

或者可以尝试画简图,如下:

手撕一棵树,检测代码

TN* BuyBTNode(int val)
{
	TN* newnode = (TN*)malloc(sizeof(TN));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->val = val;
	newnode->left = NULL;
	newnode->right = NULL;
	return newnode;
}

// 手搓一棵树
TN* CreateTree()
{
	TN* n1 = BuyBTNode(1);
	TN* n2 = BuyBTNode(2);
	TN* n3 = BuyBTNode(3);
	TN* n4 = BuyBTNode(4);
	TN* n5 = BuyBTNode(5);
	TN* n6 = BuyBTNode(6);
	TN* n7 = BuyBTNode(7);
	TN* n8 = BuyBTNode(8);


	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n4->left = n5;
	n4->right = n6;
	n5->left = n7;
	n5->right = n8;

	return n1;
}

如上,我们粗暴地将一棵树给造了出来

接下来我们将在 main 函数内将我们上述的代码都检测一遍

另,我们造出来的这棵树是长这样的:

int main()
{
	TN* root = CreateTree();
	PreOrder(root);
	printf("\n");

	printf("%d\n", TreeSize(root));

	TN* tmp = TreeFind(root, 5);
	printf("%d\n", tmp->val);

	printf("%d\n", TreeKLevel(root, 3));

	printf("%d\n", BinaryTreeLeafSize(root));

	return 0;
}

总代码 & gitee

如果有需要今天所讲内容的代码的同学,可以点击下方链接

2024-3-19-tree-递归-gitee

结语

这篇写完之后,估计就破万了

曾经跟你说过,如果你被风吹傻了的话,变成傻瓜了,那我也会把你带在身边

但是,你好像跑丢了,找不到了

跟你说过的,18篇之内会破万,算是完成了这个承诺

拜拜咯,记得照顾好自己

致一个坚强的、傻乐傻乐的女孩

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-21 11:36:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-21 11:36:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-21 11:36:01       87 阅读
  4. Python语言-面向对象

    2024-03-21 11:36:01       96 阅读

热门阅读

  1. 2024. 1华为od机试C卷【传递悄悄话】Python

    2024-03-21 11:36:01       49 阅读
  2. PDF文件底层数据结构

    2024-03-21 11:36:01       45 阅读
  3. AT+MQTTUSERCFG报错ERROR

    2024-03-21 11:36:01       39 阅读
  4. is ignored, because it exists, maybe from xml file

    2024-03-21 11:36:01       43 阅读
  5. web学习笔记(四十一)

    2024-03-21 11:36:01       41 阅读
  6. 机器学习和深度学习的区别

    2024-03-21 11:36:01       44 阅读