数据结构 | 链式二叉树【递归的终极奥义】

news2024/11/15 9:26:34

在这里插入图片描述

递归——这就是俄罗斯套娃吗😮

  • 🌳链式二叉树的结构及其声明
  • 🌳链式二叉树的四种遍历方式
    • 📕先序遍历(先根遍历)
      • 递归算法图解
    • 📕中序遍历(中根遍历)
    • 📕后序遍历(后根遍历)
    • 📕层次遍历
      • 辅助队列思维
      • 代码详解
  • 🌳链式二叉树进阶算法实现
    • 🍃求树的结点个数——难度系数【⭐⭐】
      • Way1:变量累加法
      • Way2:分治递归法
    • 🍃求树的叶子结点个数——难度系数【⭐⭐🌙】
      • 警惕空指针❗
      • DeBug调试观测
    • 🍃求树的高度——难度系数【⭐⭐⭐】
      • 规则明细及思路分析
      • 错误案例示范——怎么就是不长记性(╯▔皿▔)╯
      • 正确代码描述【递归算法图解】
    • 🍃求第K层有多少个结点——难度系数【⭐⭐⭐🌙】
      • 思路分析及规则明细
      • 代码描述
    • 🍃查找指定结点——难度系数【⭐⭐⭐】
      • 错误案例示范——有些人走着走着就散了🚶‍
    • 正确代码展示及分析
    • 🍃判断二叉树是否为完全二叉树——难度系数【⭐⭐⭐⭐】
      • 概念分析及规则明细
      • 代码描述及分析
    • 🍃销毁二叉树
      • 思路分析
      • 代码描述
  • 🌳链式二叉树OJ算法题实训
  • 🌳总结与提炼

🌳链式二叉树的结构及其声明

在上一节中,我们说到了一种数据结构——【堆】。也看到了一些有关二叉树的基本雏形,在本节中,我们就来说说有关这种链式二叉树的具体实现以及应用


  • 首先来看看它的结构声明。结构体中有三个成员,一个是当前结点的值,还有两个是指向当前结点左孩子结点的指针以及指向右孩子结点的指针
typedef int BTDataType;
typedef struct BinaryTreeNode {
	BTDataType data;
	struct BinaryTreeNode* lchild;
	struct BinaryTreeNode* rchild;
}BTNode;
  • 也就是下面这种样子
    在这里插入图片描述

🌳链式二叉树的四种遍历方式

好,知道了链式二叉树的基本结构之后,我们就要去了解如何去遍历出这棵树。对于链式二叉树的遍历方式有四种,我们逐个来讲讲

📕先序遍历(先根遍历)

规则:根——左子树——右子树

  • 首先看一下有关先序遍历的的算法算法解析图【我画了很久的/(ㄒoㄒ)/~~】

在这里插入图片描述

  • 有关先序遍历,了解了上面的规则之后可以知道,每次取遍历都是【根——左子树——右子树】,为什么说每次呢,从上图可知有关二叉树的遍历我们要使用递归来实现,也及时当访问完根之后去访问左子树,这个时候左子树不可以直接访问,而是要继续依照【根——左子树——右子树】这种规则去进行一个访问,即将【2】当做这一棵子树的根,遍历完2之后继续去遍历它的左子树,依次类推。。。
  • 因此我们可以将这棵树通过层层递归划分成若干个区域,如下图所示
  • 温馨提示:颜色有点多,可能看不太清楚,主要是为了理解递归的区间划分

在这里插入图片描述

  • 了解了这些之后,我们就可以轻而易举地写出有关先序遍历的序列了,可能你在学校里面写的就只是【1 2 3 4 5 6】这样,没错,这就是先序序列,但是在本文中,我要带你彻底搞定出递归的真正含义,所以我会将访问的空结点也表示出来,也就是NULL

在这里插入图片描述

  • 这么对照着看,应该清晰一些了,有关二叉树的遍历这一块重要的还是自己理解,多画画图,后期如果时间我录歌视频再放上来讲解一下

递归算法图解

  • 因为图太大了,所以截不下来,只能这么看了😢
    在这里插入图片描述

📕中序遍历(中根遍历)

规则:左子树——根——右子树

  • 了解了前序遍历,那中序遍历也不下话下。和先序做一个区分
    在这里插入图片描述

📕后序遍历(后根遍历)

规则:左子树——右子树——根

  • 后序遍历也是一样的套路

在这里插入图片描述

📕层次遍历

  • 当然,除了前序、中序、后序以外还有一种遍历方法就是层次遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历

在这里插入图片描述

辅助队列思维

  • 对于层序遍历呢,它和前中后序不一样,只是简单的递归,我们要使用到另一个数据结构来进行配合解决,那就是【队列】,有关这种思路的讲解,可以看看我的N叉树的层序遍历,或者是在本文的末尾中有关二叉树的面试题汇总中也要层序遍历相关的例题,这里就不作过多分析

代码详解

  • 给出代码看看。就是通过层序遍历的模板去套入即可,具体思路可看我的题解
/*层序遍历*/
void LevelOrder(BTNode* root)
{
	Qu q;
	QueueInit(&q);
	
	if (root != NULL)		//首先将非空根结点入队
		QueuePush(&q,root);

	while (!QueueEmpty(&q))
	{
		QDataType front = QueueFront(&q);	//首先获取队头结点
		printf("%d ", front->data);			//获取到之后输出打印
		QueuePop(&q);		//将其出队

		//将其左右孩子结点再从队尾入队
		if (front->lchild)
			QueuePush(&q, front->lchild);
		if(front->rchild)
			QueuePush(&q, front->rchild);
	}

	QueueDestroy(&q);
}

🌳链式二叉树进阶算法实现

刚才在初步了解了链式二叉树后学习了四种其遍历方式,但是遍历一棵链式二叉树并不是很难在二叉树这一节估计只有1到2分的难度,真正难的还在后面呢😵

🍃求树的结点个数——难度系数【⭐⭐】

Way1:变量累加法

  • 首先看到要求一棵树的结点个数,那我猜你想到的一定是去定义一个变量,然后去进行一个递归调用,每调用一次count++,但是这样的话很明显不可以,这一点我们首先得否决,在C语言函数的局部变量那一块我们有讲过,对于局部变量出了作用域就会销毁,因此你对于每一次的递归调用单独建立的栈帧都会存在这个局部变量,但是呢在栈帧销毁之后这个变量也就跟着销毁了,所以这种办法不可以取
int TreeNodeNum1(BTNode* root)
{
	if (root == NULL)
		return;
	int sz = 0;
	sz++;
	TreeNodeNum1(root->lchild);
	TreeNodeNum1(root->rchild);
	return sz;
}

  • 于是有同学呢又想出一种办法,就是将这个变量定义为静态变量,因为静态变量是存放在静态区中的,不会随着某一个函数的调用结束而销毁,因此我们可以这么写
int TreeNodeNum1(BTNode* root)
{
	if (root == NULL)
		return;
	static int sz = 0;
	sz++;
	TreeNodeNum1(root->lchild);
	TreeNodeNum1(root->rchild);
	return sz;
}
  • 可以看到,第一次的计算确实是可以计算出来这棵树有6个结点,但是在我多调用几次之后结点个数却发生了一个累加,这就是静态变量的特点,均会在上一次的基础上去进行一个运算,无论你是调用其他的什么函数或者是多调用几次,那其实可以看到这里就出现BUG了,
    我们该如何去进行修改呢?
    在这里插入图片描述
  • 其实这一块很简单,我们不需要静态变量,直接将这个变量放到函数外部,作为一个全局变量即可,因为函数内部的返回值我们不好控制,干脆就不要返回值,直接将这个记数的变量定义为全局变量,这样就很好控制了
int sz = 0;
void TreeNodeNum1(BTNode* root)
{
	if (root == NULL)
		return;
	sz++;
	TreeNodeNum1(root->lchild);
	TreeNodeNum1(root->rchild);
}
  • 我们再来测试一下。可以看到每次的结点个数算出来均是恒定不变的,就是每次在上一次调用完求值之后要将sz重新置为0

在这里插入图片描述


Way2:分治递归法

  • 很显然对于上述的这种变量累加法,并不能体现出链式二叉树的特性,接下去我们使用一种分治递归法去求解,这种方法的思路很简单也很好理解
  • 我们始终将一个树分割为三个部分,【根】【左子树】【右子树】,因此进行一个分块求解然后再加起来就可以了

情景思路分析

这个听起来是很好理解,但是递归这种结构的话对于有些同学来说还是比较晦涩难懂的,因此我们通过一个特殊的情景来分析一下

  • 疫情结束,马上就要返校复工了,此时学校需要统计有多多少人会返校,于是这件事情就交给副校长去办

  • 假设每个领导最多只能管理两个人,现在呢副校长他想要了解到两个院系有多个人,那么他就要去询问两个副院长,也就是链式二叉树所对应的左右子树;那对于副院长呢,它想要知道这个院里有多少个人,那就需要找到每个班级对应的班主任去询问;然而对于班主任呢,班级方面的事情他就交给班长去负责,统计出班里有多少人返校

  • 当班长搜集完信息后就汇报给班主任,班主任再汇报给副院长,最后副院长再汇报给副校长。副校长接收到一个副院长的统计汇报后再向另一个副院长去询问,返校人数信息,这对应的就是先访问完左子树之后再访问右子树,最后当另一个副院长也将人数汇报上来之后再加上他自己就是本次返校的人数了【当然这只是假设】

具体的图解如下
在这里插入图片描述

以下是代码

int TreeNodeNum(BTNode* root)
{
	return root == NULL ? 0 :
		TreeNodeNum(root->lchild) + TreeNodeNum(root->rchild) + 1;
}
  • 看到代码,这里是使用了一个三目运算符,当然你也可以写成if语句判断的形式,只是这样写比较简洁一些,可以看到,就是一个对左子树和右子树的分别递归,最后再加上1,也就是根节点

以下是递归算法图解

在这里插入图片描述

  • 如果你感觉自己对于链式二叉树的递归这一块不是很清楚的话,就像我这样画画递归的图解吧,可以有助于对代码的理解,因为对于递归这种结构只看代码的话是很难理解的,通过画图分析就可以清楚很多
  • 后面的题目也是一样,我会带着你画,你也可试着自己去画画看,就是递归的调用和回调的一个过程,注重递归出口就行

🍃求树的叶子结点个数——难度系数【⭐⭐🌙】

警惕空指针❗

  • 会求一棵树中的所有结点数,现在我们来进阶一下,求解树的叶子结点个数,如果不知道什么是叶子结点的话,建议看看树和二叉树的基本概念
  • 知道了如何去求解总的结点个数,那求叶子结点并不难,当然也是使用递归去完成,只是每次在递归的时候不要就是左子树和右子树的累加和,不要加上自己就行
  • 于是有同学就写了这样的代码,我们来看看有什么问题
int TreeLeafSize(BTNode* root)
{
	if (root->lchild == NULL && root->rchild == NULL)
		return 1;		//若是左右孩子均为空,则表示只有结点
	return TreeLeafSize(root->lchild) + TreeLeafSize(root->rchild);
}
  • 首先从根结点开始,左右孩子均不为空则往下递归,若是当前结点的孩子结点为空,则表示其为叶子结点,return 1返回上一级,递归完左子树之后再递归右子树,从下图可以看出,当递归2的左子树之后去递归2的右子树时间,就会出现【访问空指针】的异常

在这里插入图片描述

DeBug调试观测

这一块我们一起带VS来调试一下看看

  • 首先看到现在递归进到了2的左子树,data值为3,其左右孩子均为NULL,因为可以断定其为叶子结点,因为便返回上一级

在这里插入图片描述

  • 然后可以看到回到结点2了

在这里插入图片描述

  • 继续往右子树进行递归

在这里插入图片描述


  • 那我们该用什么办法去解决呢?这个很简单,只需要判断一下当前递归进来的结点是否为空,也就是【root == NULL】,若是空的话就返回即可
  • 所以将代码改为如下
int TreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	if (root->lchild == NULL && root->rchild == NULL)
		return 1;		//若是左右孩子均为空,则表示只有结点
	return TreeLeafSize(root->lchild) + TreeLeafSize(root->rchild);
}
  • 测试一下可以看到这棵树的叶子结点有3个

在这里插入图片描述

🍃求树的高度——难度系数【⭐⭐⭐】

规则明细及思路分析

  • 接下去,我们再将难度提升一个档次,会求结点个数了,现在去求求这棵树的高度是多少。首先我给出计算规则,防止大家跑偏

📚规则:二叉树高度 = 左子树和右子树中高度大的那个 + 1

  • 知道了规则,我们就可以通过画画图来理一下思路。可以看到我给3加了一个右孩子结点。接着从肉眼就可以看出对于根节点1来说其左子树的高度为3,右子树的高度为2,所以整棵树的高度就为3 + 1 = 4

在这里插入图片描述

错误案例示范——怎么就是不长记性(╯▔皿▔)╯

  • 接下去我们来看一个错误的案例释放,这也是很多同学会犯的错误,而且会导致很严重的重复计算
  • 可以看到这位同学就是将我们的算法图转化为了代码,递归式地去比较左右子树的高度,最后找出大的那个 + 1,但是呢
int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	return TreeHeight(root->lchild) > TreeHeight(root->rchild) ?		
		   TreeHeight(root->lchild) + 1 : TreeHeight(root->rchild) + 1;
}
  • 从运行结果可以看出是可以计算出来的,树的高度为4,但是呢却存在一个很大的隐患

在这里插入图片描述

  • 为什么这么说呢,因为这样的写法在LeetCode上提交是会【超时】的,超时是什么?是算法设计得过于复杂,程序运行的时间过长,但是那位同学就说就观光几行代码,就算是递归也就只有4高度的树而已,最多也就是十几次的递归和回调,怎么可能会超时呢?
  • 然后我是这么解释的

在这里插入图片描述

  • 这里看下来你应该可以明白了,原因就在于递归进去结点的时候比较完左右子树的高度后面将其保存再来,这样就会导致上一次结点在求求解高度的时候无法获取其左子树或者右子树的高度,因此需要再次递归下去,但是其左子树的左子树也没有保存,因此也需要再进行一个递归
  • 每往上求解一个结点的高度下面的结点就需要多递归一次,随着逐层向上求解,下面每一个结点的递归次数就是一个并不是2倍的上升,而是一个指数级别甚至阶乘级别的上升,这取决于这棵树的深度,若是深度越深,那递归层数就会越多,就会导致什么?就会导致栈内存不够然后栈溢出
  • 因此大家应该要认识到,这种写法是一种极大地浪费资源,那我们应该怎么办呢,很简单,将每次左右子树递归进去回调后的结果做一个保存即可

正确代码描述【递归算法图解】

  • 所以正确的代码应该想下面这样,将当前结点递归进去的后左右子树求出的高度做一个保存,然后对他们再进行一个比较。这样就不会出现上述的问题了,而且代码看起来还比较简洁
int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	int LHeight = TreeHeight(root->lchild);
	int	RHeight = TreeHeight(root->rchild);

	return LHeight > RHeight ? LHeight + 1 : RHeight + 1;		//返回左右子树中大的那一个再加上根结点
}

下面是算法图解

  • 图解很大,截不了图,可以看看我的架构然后自己试着画画图

在这里插入图片描述

🍃求第K层有多少个结点——难度系数【⭐⭐⭐🌙】

思路分析及规则明细

  • 好,会求结点数,会求高度,对于树的层次也有了一个概念后,我们就要再进一步去考虑更加复杂写的问题,那就是去求解第K层有多少个结点,这个我们应该怎么去求解呢?一起来画画图分析一下吧
  • 假设要求解的是【第三层有多少结点个数
    在这里插入图片描述
  • 从上图你应该可以看出我是什么意思了,这也需要一种递归的思维,对于根节点①来说,是第三层;但是对于②和④来说确实第二层,而对于③、⑤、⑥来说是第一层,只需要按照相对位置往下走就可以了。因此我们可以得出这样运算规则:

规则:
📚当k > 1 时,第k层的结点个数为其左孩子的第k - 1层 + 其右孩子的第k - 1层结点个数
📚当k == 1时,return 1

代码描述

  • 所以很明确,根据思路,就可以写出如下代码
  • 除了判断k之外,别忘了每次都要判断一个传进来的根结点是否为空,放置访问空指针。然后对于左右孩子的递归就是【k - 1】,每往其左右孩子递归一次,k就-1,递归出口就是当【k == 1】时,return 1即可
int TreeNodeNumK(BTNode* root, int k)
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	return TreeNodeNumK(root->lchild, k - 1)
		+ TreeNodeNumK(root->rchild, k - 1);
}
  • 这么看代码一定也会很抽象吧,那就试着自己对着代码去画画算法图吧,以下是我的分析过程

在这里插入图片描述

🍃查找指定结点——难度系数【⭐⭐⭐】

  • 学会了【统计】,我们来学学【查找】

错误案例示范——有些人走着走着就散了🚶‍

  • 首先我们来看看一位同学的错误代码
/*查找指定结点*/
BTNode* TreeFind(BTNode* root, BTDataType x)
{
	if (!root)			
		return NULL;
	if (root->data == x)
		return root;		//若根节点就是所要查找的结点,则直接返回
		
	TreeFind(root->lchild, x);
	TreeFind(root->rchild, x);
	//没有理解清楚递归的返回过程
}
  • 在学习了上面的统计结点个数、叶子结点个数以及第K层的结点个数,虽然代码并不复杂,而且我带大家画了递归展开图,所以有些同学就认为对于二叉树的题目都可以这么去写
  • 乍一看上面的代码其实没有什么问题,首先判断递归传入的根是否为空,然后再判断其是否就为我们要查找的结点,若都不满足就继续递归其左右子树,但是呢仔细看可以看出来,每次递归下去再回调回来的值有谁去接收呢?所以最后只会是返回一个随机值

在这里插入图片描述

  • 可以看到,在查找完之后去打印,出来的就是一个空值,因为根本就没有拿一个变量去接收一下,所以在半路就丢了

在这里插入图片描述

正确代码展示及分析

  • 这里其实和前面求解二叉树的高度是一样的,在递归进去左右子树再返回回来时,要拿一个变量去接收一下才行,这样才能保证查找到那个正确的值,而不是在半路就丢了,走散了
BTNode* TreeFind(BTNode* root, BTDataType x)
{
	/*
	* if (root == NULL)和if(!root)是等价的
	* if(root) - 结点是存在的
	* if(!root) - 结点是不存在的 --> 空的
	* if (root == NULL) -->空的
	* if (root != NULL) -->非空
	*/
	//if (root == NULL)
	//	return NULL;
	if (!root)			
		return NULL;
	if (root->data == x)
		return root;		//若根节点就是所要查找的结点,则直接返回
	/*
	* 要保存下当前查找的结点,否则递归回来没有接收返回的只会是一个空值或者随机值
	*/
	BTNode* ret1 = TreeFind(root->lchild, x);
	if (ret1)
		return ret1;		//表明在左子树中找到了该结点,返回
	BTNode* ret2 = TreeFind(root->rchild, x);
	if (ret2)
		return ret2;		//表明在右子树中找到了该结点,返回
	
	return NULL;			//左右子树均没有找到,返回NULL
}
  • 可以看到,这里在递归左右子树的时候,我分别都拿了一个指向结点的指针接收了一下,然后在递归回调的时候就可以接收到找到的这个返回值了,接着再去判断一下其是否存在,若是存在就将其return即可
  • 我们来跟着画一下它的递归算法展开图

在这里插入图片描述

  • 可以看到,有返回值去接收了,就不知道在半路丢失,可以一层一层地返回回来,最后把结果给到外界的调用
  • 我们再来看看运行结果

在这里插入图片描述


🍃判断二叉树是否为完全二叉树——难度系数【⭐⭐⭐⭐】

接下来,继续做一个进阶,在树和二叉树的基本概念中,我讲到了两种特殊的二叉树:满二叉树和完全二叉树,所以在这里我们要判断一棵树是否为完全二叉树,该如何去做呢?

概念分析及规则明细

  • 我们知道,对于完全二叉树来说,最下面一层的叶子都依次排列在改成最左边的位置上,也就是中间不可以或缺,或者说是空出一个,这样的话就不能算是完全二叉树。下面是例图📃

在这里插入图片描述

  • 所以我们就可以得出运算规则

📚若遇到的空结点后还有非空的结点,则表明其不为完全二叉树
📚若遇到的空结点后均为空,则表示其为完全二叉树

  • 有了这个运算规则之后呢我们就可以去将其转化为代码了,这个逻辑是要嵌套在二叉树的层序遍历中的,层序遍历这一块的题目我也是有做过相关的题解,而且在下面模块的OJ算法题实训中也有讲到,可以去看看,直接套用模板即可

代码描述及分析

首先给出代码

/*判断二叉树是否为完全二叉树*/
bool TreeComplete(BTNode* root)
{
	Qu qu;
	QueueInit(&qu);

	if (root)
		QueuePush(&qu, root);		//首选入队队头结点

	while (!QueueEmpty(&qu))
	{
		QDataType front = QueueFront(&qu);
		QueuePop(&qu);

		//判断当前队列结点是否为空
		if (front == NULL)
			break;		//若为空,则break跳出循环进行判断
		else
		{
			//若不为空,则入队其左右孩子
			QueuePush(&qu, front->lchild);
			QueuePush(&qu, front->rchild);
		}
	}

	//此处判断是否为完全二叉树
	/*
	* 1.若遇到的空结点后还有非空的结点,则表明若遇到的空结点后均为空,则表示其为完全二叉树其不为完全二叉树
	* 2.
	*/
	if (!QueueEmpty(&qu))
	{			
		QDataType front = QueueFront(&qu);
		if (front != NULL)
		{				//若是在队列中取到了非空的结点,则表示不为完全二叉树
			QueueDestroy(&qu);		//提前return要记得销毁队列
			return false;
		}

	}
	return true;
	QueueDestroy(&qu);
}
  • 看到上述代码,应该可以知道为什么这个算法可以被赋四颗星了吧,相比其他进阶算法来说确实需要一些二叉树的基本能力
  • 但是呢,有了我给你的这个思路之后再去写代码其实也不是什么难事,我们只需要在一些地方做一个修改就可以了。首先第一个地方就是在刚获取到当前队头结点的时候,此时不要着急去入左右孩子结点,需要做一些改动,而且对于层序遍历所涉及的OJ题基本也是在这个地方去做一个改动
  • 什么时候跳出循环呢? 但是在内层进行判断肯定不行,最好做一个分离,外层判断,那也就需要使用到【break】,跳出当前的循环,那要什么时候跳出呢?刚才看了那张图应该可以知道有一个转折点应该是在层序遍历到NULL的时候,此时我们应该跳出循环
  • 那要怎么判断呢? 回忆完全二叉树的定义可以知道,但凡你遇到NULL了之后就相等于是断层了,后面若是再出现有结点那就说明不是完全二叉树,我们可以根据这点去判断队列是否为空,然后知晓后面是否还有结点需要遍历。这个就是跳出循环后的判断逻辑了

注意:在判断出不为完全二叉树后提前【return false】了,就需要将队列销毁,否则会造成内存泄漏


  • 再来看看运行结果

在这里插入图片描述
在这里插入图片描述

🍃销毁二叉树

思路分析

  • 对一棵二叉树进行了一系列的操作之后,我们要去肯定要去销毁这棵二叉树,否则也会造成内存泄漏
  • 但是这要怎么去完成呢?从根节点开始往下销毁吗?设想你现将根节点销毁了,那左右子树不是找不到了,就和你删除链表结点是一个道理,改变了这个指向原本的指向也就是找不到了
  • 因此我们不应该从根结点开始,应该先从左右子树开始,那对于左子树来说它又可以作为一个根结点,依旧是需要先去销毁其左右子树,右子树也是一样,因此这就又成了一个递归的问题。而且是要销毁掉左右子树之后再去销毁根结点,所以这就相当于是一种后序遍历的思维

在这里插入图片描述

代码描述

  • 来展示一下代码
/*销毁二叉树*/
void DestroyTree(BTNode* root)
{
	if (root == NULL)		//if(!root)
		return;

	DestroyTree(root->lchild);
	DestroyTree(root->rchild);

	free(root);
}
  • 代码很简洁,当还没遍历到空树时,就一直递归遍历其左子树,然后进行销毁,接着return回去销毁右子树,最后再销毁根结点
  • 但是这里有一点要注意的是对于内部的销毁是无法因此外部变化的,这个相信你在看了本文后也有所领会,因此我们要在外界函数调用完后再对指向根节点的指针置空,防治野指针
DestroyTree(n1);
n1 = NULL;		//内部置空外部也要置空【形参改变不影响实参】

🌳链式二叉树OJ算法题实训

本模块请于我另一个专栏【LeetCode算法笔记】进行观看

也可直接点击此链接

🌳总结与提炼

学习完了链式二叉树,我们来总结一下所学习的知识点📖

  • 首先我们了解了这种链式二叉树的数据结构,知道了它由左右孩子以及结点值组成
  • 接下去了解有关链式二叉树的四种遍历方式,分别是先序、中序、后序以及层次遍历,这里主要是使用递归的形式去求解,非递归的思路我们后续在【数据结构进阶】中再做讲解
  • 然后呢我们在了解了基本的遍历算法后对二叉树有了一些进阶算法的训练,分别是【求解树的结点个数】、【叶子结点个数】、【高度】、【第K层结点个数】、【查找指定结点】、【判断是否为完全二叉树以及销毁】等,有了这些进阶训练后,对于二叉树这种数据结构也有了进一步地了解
  • 接下去就是实战训练,我们通过十余道OJ算法题的训练,做了一些有关二叉树的实际应用,对二叉树更上一个台阶
  • 二叉树这种数据结构无论是在笔试、面试或者考研中都是非常重要的,作为读者的你是否有感受到呢?

以上就是本文所要描述的所有内容,感谢您对本文的观看,如有疑问请于评论区留言或者私信我都可以🍀

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

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

相关文章

TIA PORTAL 导出导入数据块

1.导出:选择要导出的数据块鼠标右键-->从块生成源-->仅所选块或包含所有关联块-->最后选择数据块的存储路径保存 2.导入:选外部源文件-->添加新的外部文件-->选择要导入的数据块文件-->单击文件鼠标右键-->从源生成块,最…

Vue3——ref(),reactive(),watch(),computed()的使用

都需要先引入才能使用 ref()函数 作用:创建一个响应式变量,使得某个变量在发生改变时可以同步发生在页面上 模板语句中使用这个变量时可以直接使用变量名来调用,在setup内部调用时则需要在变量明后面加上一个.value获取它的值,原…

记录一次使用卷积神经网络进行图片二分类的实战

写在前面 笔者目前就读的专业是软件工程,并非人工智能专业,但是由于对人工智能有兴趣,于是课下进行了一些自学。正巧最近有些闲暇时间,就想着使用自学的内容做个小型的实战。这篇文章的主要目的也就是从一个入门者的角度&#xf…

【C++】list

本期就来讲讲list的使用技巧 文章目录list的介绍及使用list的介绍list迭代器失效list的模拟实现list与vector的对比我们前面知道迭代器是一个像指针一样的东西,但是在C里面,出来string和vector,其他类都不能 将迭代器当成指针使用&#xff0c…

二叉树的非递归与相关oj

🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸 文章目录一、二叉树相关oj①二叉搜索树与双向链表②前序遍历和中序遍历构造二叉树二、二叉树的非递归①前序遍历非递归②中序遍历非递归③后序遍历非…

简单的算法思想 - 利用快慢指针解决问题 - 寻找链表中的中间节点,回文序列,倒数第k个节点 - 详解

文章目录1. 寻找链表中倒数第K个节点1.1. 思路分析1.2 代码实现2. 寻找链表中的中间结点2.1 思路概述2.2 代码实现3. 链表的回文结构3.1 思路分析3.2 代码实现总结✨✨✨学习的道路很枯燥,希望我们能并肩走下来! 本文通过寻找链表中的中间节点&#xff0…

汽车托运网址

开发工具(eclipse/idea/vscode等): 数据库(sqlite/mysql/sqlserver等): 功能模块(请用文字描述,至少200字): 基于Web的汽车托运网站的设计与实现 网站前台:关于我们、联系我们、公告信息、卡车类型、卡车信息、运输评论…

【语音处理】一种增强的隐写及其在IP语音隐写中的应用(Matlab代码实现)

👨‍🎓个人主页:研学社的博客 💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜…

Effective Objective-C 2.0学习记录(一)

48.多用块枚举,少用for循环for循环快速枚举(快速遍历)基于块的遍历方式在编程中经常需要用到列举collection(NSArray、NSDictionary、NSSet等)中的元素,当前的Objective-C语言有多种办法实现此功能&#xf…

【专栏】核心篇09| 怎么保证缓存与DB的数据一致性

计算机类PDF整理:【详细!!】计算机类PDF整理 Redis专栏合集 【专栏】01| Redis夜的第一章 【专栏】基础篇02| Redis 旁路缓存的价值 【专栏】基础篇03| Redis 花样的数据结构 【专栏】基础篇04| Redis 该怎么保证数据不丢失(上…

Python -- 模块和包

目录 1.Python中的模块 1.1 import 1.3 from...import * 1.4 as别名 2.常见的系统模块和使用 2.1 OS模块 2.2 sys模块 2.3 math模块 2.4 random模块 2.5 datetime模块 2.6 time模块 2.7 calendar模块 2.8 hashlib模块 2.9 hmac模块 2.10 copy模块 3.pip命令的使…

【机器学习---01】机器学习

文章目录1. 什么是机器学习?2. 机器学习分类2.1 基本分类2.2 按模型分类2.3 其他分类(不重要)3. 机器学习三要素4. 监督学习的应用(分类、标注、回归问题)1. 什么是机器学习? 定义:给定训练集D,让计算机从一个函数集合F {f1(x)&…

虚拟机打不开,提示“此主机不支持虚拟化实际模式”的详细解决方法

虚拟机打不开,提示“此主机不支持虚拟机实际模式”的解决方法 一、第一种情况安装/启动虚拟机失败, 在VMWare软件中,安装/启动虚拟机时,如果出以类似以下的错误提示: 出现该提示是由于电脑不支持虚拟化技术或是相关功…

IDEA报错:类文件具有错误的版本 61.0,应为52.0

springboot项目启动报错: 类文件具有错误的版本 61.0,应为52.0 请删除该文件或确保该文件位于正确的类路径子目录中 查阅了网上的很多资料,普遍原因说是springboot版本过高,高于3.0 需要在pom文件中降低版本 也有说是idea的maven配置java版…

网购商城网站

开发工具(eclipse/idea/vscode等): 数据库(sqlite/mysql/sqlserver等): 功能模块(请用文字描述,至少200字):

【Python机器学习】层次聚类AGNES、二分K-Means算法的讲解及实战演示(图文解释 附源码)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 层次聚类 在聚类算法中,有一类研究执行过程的算法,它们以其他聚类算法为基础,通过不同的运用方式试图达到提高效率,避免局部最优等目的,这类算法主要有网格聚类和…

easypoi导入excel空指针异常

问题描述 前端页面停留在导入页面,通过后端返回的接口,确认后端已经抛出异常查看系统调用错误日志为 java.lang.NullPointerException: nullat org.apache.poi.xssf.usermodel.XSSFClientAnchor.setCol2(XSSFClientAnchor.java:231)at org.apache.poi.…

基于EKF的四旋翼无人机姿态估计matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 卡尔曼滤波是一种高效率的递归滤波器(自回归滤波器),它能够从一系列的不完全包含噪声的测量中,估计动态系统的状态。这种滤波方法以它的发明者鲁道夫E卡尔曼(R…

Android koin

1.源码地址 1.源码地址 2.作用 1.让代码看起来更简洁 现在是这样创建对象的 2.解耦 我们有一个类,然后有100个地方使用它,这个时候如果我们要修改构造参数,加入一个参数,那么我们就要修改100个地方;如果过了一个…

怎样让chatGPT给你打工然后月入过千?

前言 chatGPT最近火出圈了,怎么薅一个文字模型给你打工呢? 这个UP给了个思路:哔哩哔哩 emmm有点尴尬,可能是热度比较高,b站的视频作者自己下架了。 总结一下: 薅的对象百度文库创作中心:地址…