数据结构--二叉树(二)

链式二叉树

        链式二叉树是链式树集合中的一种,该树的每个根节点最多只有两个孩子节点,我们一般用左右孩子来称呼,在初学链式二叉树时,由于大家对链式二叉树的结构掌握还不够深入,为了降低本章的学习难度及成本,我们先手动创建一棵简易二叉树,快速进入二叉树的操作学习中,等所有基本操作大家都掌握后,我们再来了解如何创建链式二叉树。

        对于链式二叉树的结构代码如下:

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

        接下来是手动创建一棵简易二叉树:

BTNode* BuyNode(BTDataType data)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		return NULL;
	}
	node->data = data;
	node->left = node->right = NULL;
	return node;
}


BTNode* CreatBinaryTree()
{
    BTNode* node1 = BuyNode(1);
    BTNode* node2 = BuyNode(2);
    BTNode* node3 = BuyNode(3);
    BTNode* node4 = BuyNode(4);
    BTNode* node5 = BuyNode(5);
    BTNode* node6 = BuyNode(6);

    node1->left = node2;
    node1->right = node4;
    node2->left = node3;
    node4->left = node5;
    node4->right = node6;

    return node1;
}

        构建的二叉树结构如图;

我们先来回顾一下二叉树的概念,二叉树是:

1. 空树。

2. 非空:根结点,根结点的左子树、根结点的右子树组成的。

        从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中大多数代码基本都是按照该概念实现的。

二叉树的遍历

        学习二叉树结构,最简单的方式就是遍历。,所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问节点所做的操作依赖于具体的应用问题。遍历是学习二叉树里最重要的运算之一,也是二叉树进行其他进阶运算的基础。

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

1.前序遍历(Preorder Traversal)--访问根结点的操作发生在遍历其左右子树之前。

2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

        由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

        前序遍历:

void PreOrder(BTNode* root); 

        前序遍历的代码很简单,但因为是通过递归来实现的,所以思维量较大,我们通过代码讲解一下:

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

        各位看到代码是否有眼前一亮呢,为何代码如此简单,别急,我们来通过图分析分析:

        前序遍历的要求是先遍历根节点,然后遍历左子树,最后遍历右子树。开始时,根节点的值为1,首先打印到控制台上,随后进入左子树进行遍历,左子树的根节点值为2,打印到控制台上,进入左子树进行遍历, 左子树的根节点值为3,打印到控制台上,进入左子树进行遍历,发现左子树的根节点为空,返回,进入到右子树进行遍历,发现右子树的根节点为空,返回。根节点值为3的函数已经运行结束,返回到根节点值为2的子树进行该子树的右子树遍历,根节点为空,返回。根节点为2的函数运行结束,返回到根节点值为1的二叉树中向下运行,进入右子树遍历,右子树根节点的值为4,打印到控制台上,进入左子树遍历,左子树的根节点值为5,打印到控制台上,进入左子树进行遍历, 左子树的根节点为空,返回,进入右子树遍历,右子树的根节点为空,返回。根节点值为5的函数运行结束,返回到根节点为4的函数中继续向下执行,进入右子树遍历,右子树根节点为6,打印到控制台上,进入左子树遍历,左子树的根节点为空,返回,进入右子树遍历,右子树的根节点为空,返回。根节点为6的函数执行结束,返回,根节点为4的函数执行结束,返回,根节点为1的函数执行结束,返回,函数结束。当然,之前节点为空的再返回之前会打印N字符表示该节点为空。

        聊到这里,我们不妨来了解一下递归函数中栈帧的创建和销毁过程,函数栈帧创建时需要内存提供空间存放栈帧,且存放是由高地址向低地址存放,由esp和ebp来记录函数栈帧两端的地址。通过不同的指令对寄存器进行操作,从而完成了函数栈帧的创建和销毁。

        具体细节各位可以看本篇文章:函数栈帧的创建和销毁(编程底层原理)-CSDN博客

        在这里我想要强调的是,每一个函数栈帧的开辟都需要开辟相应的空间,而函数递归是一种减少代码量,增加思维量和计算机指令的方式,如果递归层数过大可能会导致栈溢出(Stack Overflow)等问题,因此我们在后续解决递归问题时应尽可能的减少递归层次以避免该问题。

        还有一点各位要了解:我们调用的函数栈帧销毁是会将那块空间返还给内存,当进行下一次调用时,我们使用的空间是即是刚销毁的那块空间,也就是说,函数栈帧开辟销毁的空间是可以重复利用的。

        中序遍历

void InOrder(BTNode* root);

        代码如下:

        中序遍历的要求为先遍历左子树,然后遍历根节点,最后遍历右子树。

//中序遍历
void InOrder(BTNode* root)
{
	//根为空打印N并返回
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	//根不为空,先遍历左子树,后打印根节点,再遍历右子树
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}

         通过前序遍历,我想各位已经对递归函数的过程有了一定的了解,我希望各位可以了解前序遍历的过程后,独立去画一下中序遍历的过程,来加深印象和理解。

        后序遍历

void PostOrder(BTNode* root);

        代码如下: 

//后序遍历
void PostOrder(BTNode* root)
{
	//根为空打印N并返回
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	//根不为空,先遍历左子树,后遍历右子树,再打印根节点
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->data);
}

        后序遍历的要求为先遍历左子树,然后遍历右子树,最后遍历根节点。开始时,根节点的值为1,进入到左子树遍历,左子树根节点的值为2,进入到左子树遍历,左子树的根节点值为3,进入到左子树遍历,根节点为NULL,打印N返回,进入到右子树遍历,根节点为NULL,打印N返回,随后打印值为3的根节点,返回上层函数,进入右子树,为空返回,打印2,返回到根节点为1的函数,进入右子树,根节点为4,进入左子树,根节点为5,进入左子树,为空返回,进入右子树为空返回,打印5,返回到根节点为4的函数,进入右子树,根节点为6,进入左子树,为空返回,进入右子树,为空返回,打印6返回,打印4返回,打印1,结束。

二叉树基本操作

        这样我们的三种遍历方式就结束了,接下来我们先看四个基本操作:

// 二叉树结点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

        二叉树结点个数

        这道题的思路有很多种,我们来一 一对比这几种方法的优劣:

        法一:全局变量 / (static 局部变量)法

        这两种方法比较相似,我们一起看:

//局部变量计算
int BinarySreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	//static修饰局部变量 == 全局变量
	static int size = 0;
	size++;
	BinarySreeSize(root->left);
	BinarySreeSize(root->right);
	return size;
}

//全局变量计算
int num = 0;
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	num++;
	BinaryTreeSize(root->left);
	BinaryTreeSize(root->right);
	return num;
}

        这两种方式都存在一个很大的弊端,如果我们通过printf多次调用本函数,会发现节点的个数会成倍增加,只有第一次调用函数的结果是对的,因此我不建议各位使用法一去解决此类问题。

        法二: 传参计算法:        

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

	return (*pi);
}

        法二解决了法一中的问题,但是代价为法二多传了一个指针变量,并且,我们需要再调用函数之前将变量赋值为0。这样代码依旧略显冗余,因此就有了法三。

        法三:

        思路为当根节点为空时,返回0。根节点非空就返回根节点(1) + 左子树中的节点数 + 右子树中的节点数,在进行多次递归之后,我们就能得到节点的个数。代码如下: 

// 二叉树结点个数
int BinaryTreeSize(BTNode* root)
{
	//空节点返回0
	if (root == NULL)
		return 0;
	//非空节点返回:左子树节点个数+右子树节点个数+1
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

        二叉树叶子结点个数      

        这个操作与上面类似,只是之前是根节点存在加1,现在改成了,根节点存在且根节点不存在子树时加1。代码如下:

// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{
	//空节点返回0
	if (root == NULL)
		return 0;
	//非空节点且无孩子节点返回1
	if (root->left == NULL && root->right == NULL)
		return 1;
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

        二叉树第k层结点个数

        我们要将操作分为三个过程。

1.根节点为空时,返回0。

2.根节点不为空,如果k为1,直接返回1。

3.根节点不为空,如果k>1,返回该二叉树的左子树第k-1层的节点个数与右子树第k-1层的节点个数。

代码如下: 

// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	//空树返回0
	if (root == NULL)
		return 0;
	//非空树返回,如果k==1,返回1
	if (k == 1)	//root != NULL
		return 1;
	//非空树且,k > 1时
	return BinaryTreeLevelKSize(root->left, k - 1) 
        + BinaryTreeLevelKSize(root->right, k - 1);
}

        二叉树查找值为x的结点

        这个操作在细节上非常容易出问题,我们将操作分为四步:

1. 根节点为空,返回NULL。

2. 根节点不为空,且根节点的值等于要查找的值,返回根节点。

3. 如果上方条件不成立,进入左子树寻找x节点,若找到,返回;没找到进入右子树寻找。

4. 遍历二叉树后,没有找到,返回NULL。

        这里要注意的是,很多人不明白为什么要用指针变量去接收函数的返回值之后再返回,因为我们在开始第三步后,如果找到了想要的节点,就会返回,而如果没有变量接收该节点继续返回上一层函数,就会继续往下执行操作,最终可能返回的依旧是NULL,因此接收返回值的操作至关重要,不仅能在找到节点后迅速返回,还避免了多层嵌套可能导致的栈溢出风险。

代码如下: 

// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	//空节点返回空
	if (root == NULL)
		return NULL;
	//非空节点比较数据,相同返回节点
	if (root->data == x)
		return root;
	//不相同继续遍历左、右子树寻找
	BTNode* ret1 = BinaryTreeFind(root->left, x);
	if (ret1)
		return ret1;
	BTNode* ret2 = BinaryTreeFind(root->right, x);
	if (ret2)
		return ret2;
	//都遍历完还没有,返回NULL
	return NULL;
}

二叉树经典OJ题目 

        下面我们来看一些题目巩固一下所学的知识:

单值二叉树

        如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。只有给定的树是单值二叉树时,才返回 true;否则返回 false

提示:

  1. 给定树的节点数范围是 [1, 100]
  2. 每个节点的值都是整数,范围为 [0, 99] 。

解:

        既然要判断是否是单值二叉树,那根节点的值就必然要与它的子节点的值相同,因为我们知道a==b,b==c,则a==c的道理,我们就可以通过递归继续向下比较根与子节点的值,如果不相等就返回false,直到子树的根节点为空,返回true。又因为是整棵树的值都相等是才算单值,因此我们判断左右子树时用到的是&&符号。代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

bool isUnivalTree(struct TreeNode* root) {
    if(root == NULL)
        return true;
    if(root->left && root->left->val != root->val)
        return false;
    if(root->right && root->right->val != root->val)
        return false;
    return isUnivalTree(root->left) && isUnivalTree(root->right);
}

检查两棵树是否相同

         给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

 提示:

  • 两棵树上的节点数目都在范围 [0, 100] 内
  • -10^4 <= Node.val <= 10^4

解:

         这道题是很经典的一道基础题,在未来很多复杂进阶题目中都会见到它的身影。判断两个树是否相同,首先我们要看的就是根节点问题,如果两个根节点都不存在节点,当然就是相同的,我们返回true;如果只有其中一个的根节点存在,就是不同的,我们返回false;如果有两个根节点都存在,我们就需要去比较两个根节点的值是否相同,如果不同就返回false;如果相同,我们就继续判断他们的左子树和右子树,两个判断之间依旧是&&符号。代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    if(p == NULL && q == NULL)
        return true;
    if(p == NULL || q == NULL)
        return false;
    if(p->val != q->val)
        return false;
    return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
}

对称二叉树

        给你一个二叉树的根节点 root , 检查它是否轴对称。

提示:

  • 树中节点数目在范围 [1, 1000] 内
  • -100 <= Node.val <= 100

解:

         这道题其实是上面判断相同树的一种变形,既然是对称树,那就存在镜像,我们在这个点上下手,问题不攻自破。首先判断根节点是否存在,不存在直接返回 true,随后就是判断相同树的那一套,只是我们将递归函数那边的参数调整了位置,让左子树的左孩子和右子树的右孩子比较,左子树的有孩子与右子树的左孩子比较,进行递归后即可得到结果。这里要注意的是,我们无需再从isSymmetric函数里再去递归。

        本质上就是先判断树的根节点是否存在,不存在就返回;存在就对根节点的左右子树进行镜像树判断。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

bool isSameTree(struct TreeNode* p, struct TreeNode* q) 
{
    if(p == NULL && q == NULL)
        return true;
    if(p == NULL || q == NULL)
        return false;
    if(p->val != q->val)
        return false;
    return isSameTree(p->left,q->right) && isSameTree(p->right,q->left);
}

bool isSymmetric(struct TreeNode* root) {
    if(root == NULL)
        return true;
    return isSameTree(root->left, root->right);
}

二叉树的前序遍历

        给你二叉树的根节点 root ,返回它节点值的前序遍历。 

解:

        因为前、中、后序遍历代码基本一致,这里只讲前序遍历的一道例题。

        题目给出的原函数是preorderTraversal(struct TreeNode* root, int* returnSize),函数给了一个returnsize用于记录返回数组的长度,我们解题的第一想法通常是,遍历一个节点,++一下returnsize,这样没问题,但是有一种更好的方法,就是在创建数组之前我们遍历一遍树的根节点数,然后在开辟数组空间时就会减少不必要的空间消耗。正好也用到了我们之前的基本操作TreeSize函数,然后通过前序遍历将所有节点的val值放入数组,最后返回数组就可以了。

        这里要注意的一点是我们在给preOrder传参时,i一定要传地址,因为在递归函数中,形参的改变不会影响实参,会影响遍历过程。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */

int TreeSize(struct TreeNode* root)
{
    return root == NULL? 0 : TreeSize(root->left)+TreeSize(root->right) + 1;
}

void preOrder(struct TreeNode* root, int* a, int* pi)
{
    if(root == NULL)
        return;
    a[(*pi)++] = root->val;
    preOrder(root->left,a,pi);
    preOrder(root->right,a,pi);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    *returnSize = TreeSize(root);
    int* a = (int*)malloc(sizeof(int)*(*returnSize));
    int i = 0;
    preOrder(root,a,&i);
    return a;
}

另一棵子树的子树

        给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。

        二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

 提示:

  • root 树上的节点数量范围是 [1, 2000]
  • subRoot 树上的节点数量范围是 [1, 1000]
  • -10^4 <= root.val <= 10^4
  • -10^4 <= subRoot.val <= 10^4

 解:

        这道题同样延续了相同树的题型,判断一棵树是不是另一棵树的子树,只需要从根节点开始判断,如果两棵树相等就返回 true, 不相等就继续遍历它的左右子树,直到遍历完所有子树,根节点为空后,如还没有找到相同树,就返回false。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    if(p == NULL && q == NULL)
        return true;
    if(p == NULL || q == NULL)
        return false;
    if(p->val != q->val)
        return false;
    return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
    if(root == NULL)
        return false;
    
    if(isSameTree(root,subRoot))
        return true;
    
    return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
}

二叉树的创建及遍历

        在了解了链式二叉树的一些基本操作和经典OJ题目后,我想各位已经对二叉树有了一定的感觉,下面我们就来看看如何通过数组中存放的数据来创建链式二叉树。我们通过一道题目来给搭建演示:

        编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。

输入描述:

        输入包括1行字符串,长度不超过100。

输出描述:

        可能有多组测试数据,对于每组数据, 输出将输入字符串建立二叉树后中序遍历的序列,每个字符后面都有一个空格。 每个输出结果占一行。

解:

         既然要接收一行字符串,自然要先创建数组。通过scanf接收后,进行前序二叉树的创建,我在创建二叉树函数中传入了一个数组首地址和一个变量的地址,分别用于接收数组和变量 i(作为数组元素的下标使用),进入函数后,首先判断数组当前下标的元素是否为空(#),为空的话就++下标并返回,不为空就继续向下执行,申请节点,根节点赋值,递归创建左子树,递归创建右子树,这都是之前见过的,这里不在多说,最后返回根节点。创建好之后,进入中序遍历函数,这里前面有。最后打印节点,结束。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//创建链式二叉树结构体
typedef char BTDataType;
typedef struct BinaryTreeNode
{
    BTDataType data;
    struct BinaryTreeNode* left;
    struct BinaryTreeNode* right;
}BTNode;

BTNode* BinaryTreeNodeCreate(BTDataType* a, int* pi)
{
    if(a[*pi] == '#')
    {
        (*pi)++;
        return NULL;
    }

    BTNode* root = (BTNode*)malloc(sizeof(BTNode));
    root->data = a[(*pi)++];
    root->left = BinaryTreeNodeCreate(a,pi);
    root->right = BinaryTreeNodeCreate(a,pi);

    return root;
}

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

int main()
{
    //创建数组,接收字符串
    BTDataType a[100];
    scanf("%s",a); 
    //二叉树的创建
    int i = 0;
    BTNode* root = BinaryTreeNodeCreate(a,&i);
    //中序遍历
    InOrder(root);
    return 0;
}

二叉树与队列的结合

        层序遍历

        在讲解之前我们需要进行一些队列代码的处理,因为之前我们队列中存储的值是int类型的,但是我们在树中的存放的节点是以链表的形式,因此在不破坏结构为前提下,我们需要将之前定义的QDataType转换为struct BinaryTreeNode*的指针类型,并把队列的头文件包含过去,这样我们就可以进行后续操作了。

        在前面将前、中、后序遍历的时候,我们采用的是递归遍历树节点的方式完成的。但是层序遍历有所不同,我们不能再顺着根节点去遍历,而是遍历完一层之后,才能去遍历下一层节点即广度优先算法。因此我们需要用到队列的功能,首先将根节点入队列,将根节点的值赋值给front之后删除队列中的节点,打印front的值,如果根节点的左孩子存在,入队列,如果根节点的有孩子存在,也入队列,继续上述操作,我们就能将所有的节点按,层序遍历的方式打印出来。代码如下:

//层序遍历
void TreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q,root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%c ", front->data);
		if (front->left)
			QueuePush(&q, front->left);
		if (front->right)
			QueuePush(&q, front->right);
	}
}

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

        判断是否为完全二叉树的思路本质上和层序遍历是一样的,区别在于层序遍历找到空节点是跳过(不入队列)的,但是在判断完全二叉树时,我们要讲空节点也入队列,这是我们判断的关键条件,将根节点入队列,接着出队列时带入根节点的两个子节点,当出队列的根节点为空时,跳出循环。进入下一步,遍历队列中剩余的节点,如果都为空节点,说明是完全二叉树,如果存在非空节点,说明不是完全二叉树。

        对于其中的一些常见疑问,大家可以画图去了解,我在这里讲一个常见问题:如果遇到空节点时,还有非空节点没有入队列怎么办,这个担心是对的,但是我们一画图就能看出,这种情况的担心是多余的。

        我们从上图中可以看出,遇到第一个空节点时,即便是最下面的节点也入队列了,所以是完全可以判断的。可能大家还会想到一种情况:

        如果树节点是这样的那最后这个不就入不了队列吗,没错,但是这个节点入不入队列都是无所谓的,因为它的双亲节点能够入队列就已经证明了这棵树是否为完全二叉树,因此我们可以把它忽略不计。 

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		
		if (front == NULL)
			break;
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}

二叉树的销毁

        二叉树的销毁比较简单,通过后序遍历释放根节点即可完成。代码如下:

// 二叉树销毁
void BinaryTreeDestory(BTNode* root)
{
	//后序遍历销毁二叉树
	if (root == NULL)
		return;
	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->right);
	free(root);
}

         到此,我们的二叉树的学习就要告一段落了,但是二叉树的内容远不止于此,我们会在之后带大家走入更深层次的二叉树学习,如:AVL树、平衡搜索树、红黑树以及B树系列,敬请期待,我们下期见。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pytorch构建模型训练数据集

pytorch构建模型训练数据集 pytorch构建模型训练数据集1.AlexNet:1.1.导入必要的库&#xff1a;1.2.数据预处理和增强&#xff1a;1.3.加载数据集&#xff1a;1.4.划分测试集和训练集&#xff1a;1.5.创建数据加载器&#xff1a;1.6.加载AlexNet模型&#xff1a;1.7.修改模型以…

Oracle EBS AP发票创建会计科目错误:子分类帐日记帐分录未按输入币种进行平衡

系统版本 RDBMS : 12.1.0.2.0 Oracle Applications : 12.2.6 问题症状: 提交“创建会计科目”请求提示错误信息如下: 中文报错: 该子分类帐日记帐分录未按输入币种进行平衡。请检查日记帐分录行中输入的金额。 英文报错:The subledger journal entry does not balance i…

电机专用32位MCU PY32MD310,Arm® Cortex-M0+内核

PY32MD310是一颗专为电机控制设计的MCU&#xff0c;非常适合用做三相/单相 BLDC/PMSM 的主控芯片。芯片采用了高性能的 32 位 ARM Cortex-M0 内核&#xff0c;QFN32封装。内置最大 64 Kbytes flash 和 8 Kbytes SRAM 存储器&#xff0c;最高48 MHz工作频率&#xff0c;多达 16 …

Suryxin’s ACM退役记

序 我的记忆力很差&#xff0c;经历过的很多事情都已经记不太清了&#xff0c;其中有很多美好回忆也已经消散&#xff0c;我很惋惜没能留存一些照片和声音或是文字供我怀念&#xff0c;这就像《泰坦尼克号》一样&#xff0c;露丝和杰克感人肺腑的爱情故事&#xff0c;最后也仅…

2024年最新的软件测试面试总结(答案+文档)

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 测试技术面试题 1、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 参考答案&#xff1a; 兼容测试主要是检查软件在不同的硬件平台、软件平…

[word] 在Word中插入分页符 #经验分享#经验分享#笔记

在Word中插入分页符 在 Word中插入分页符&#xff1f;教大家如何在正文中快速的插入分页符。 ? ? ?1、在正文中选择我们要分页的位置。 ? ? ?2、点击插入&#xff0c;选择分页功能里面的“分页符”功能&#xff0c;即可成功在我们选择的位置进行分页。 ? ? ? ?以上…

PICRUSt2在微生物功能预测分析中的应用解读

谷禾健康 微生物组学研究现已超越微生物群落组成分析得到更广泛的使用。大量的人类微生物组研究证据表明&#xff0c;肠道微生物组的功能变化对炎症和免疫反应的影响起到关键的影响作用。 16S rRNA分析是微生物组研究作为最常用便捷且具有成本效益的测量技术&#xff0c;用于分…

从技术到产品:以客户为中心的产品研发之路

一、引言 在快速发展的商业环境中&#xff0c;产品作为连接企业与市场的桥梁&#xff0c;其重要性不言而喻。从摸着石头过河搞产品&#xff0c;到广泛传播NPDP&#xff08;新产品开发流程&#xff09;理念&#xff0c;产品研发的道路经历了从直觉驱动到系统思维的转变。本文将…

【SpringBoot】SpringBoot整合RabbitMQ消息中间件,实现延迟队列和死信队列

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 一、&#x1f525;死信队列 RabbitMQ的工作模式 死信队列的工作模式 二、&#x1f349;RabbitMQ相关的安装 三、&#x1f34e;SpringBoot引入RabbitMQ 1.引入依赖 2.创建队列和交换器 2.1 变量声明 2.2 创建…

[经验] 昆山教育网(昆山教育网中小学报名) #媒体#职场发展#微信

昆山教育网&#xff08;昆山教育网中小学报名&#xff09; 昆山教育局网站 网站&#xff1a;昆山市教育局 昆山市教育局全面贯彻执行党和国家的教育方针、政策&#xff0c;落实有关教育工作的法律、法规&#xff1b;负责制定本市教育工作的实施意见和措施&#xff0c;并监督…

WEB漏洞服务能提供哪些帮助

在数字化浪潮的推动下&#xff0c;Web应用程序已成为企业展示形象、提供服务、与用户进行交互的重要平台。然而&#xff0c;随着技术的飞速发展&#xff0c;Web应用程序中的安全漏洞也日益显现&#xff0c;成为网络安全的重大隐患。这些漏洞一旦被恶意攻击者利用&#xff0c;可…

C++笔记之一个函数多个返回值的方法、std::pair、std::tuple、std::tie的用法

C++笔记之一个函数多个返回值的方法、std::pair、std::tuple、std::tie的用法 —— 2024-06-08 杭州 code review! 文章目录 C++笔记之一个函数多个返回值的方法、std::pair、std::tuple、std::tie的用法一.从一个函数中获取多个返回值的方法1. 使用结构体或类2. 使用`std::t…

C# MES通信从入门到精通(11)——C#如何使用Json字符串

前言 我们在开发上位机软件的过程中,经常需要和Mes系统进行数据交互,并且最常用的数据格式是Json,本文就是详细介绍Json格式的类型,以及我们在与mes系统进行交互时如何组织Json数据。 1、在C#中如何调用Json 在C#中调用Json相关的对象的话,需要引用Newtonsoft.Json的dl…

三十六篇:未来架构师之道:掌握现代信息系统典型架构

未来架构师之道&#xff1a;掌握现代信息系统典型架构 1. 引言 在企业的数字化转型浪潮中&#xff0c;信息系统架构的角色变得日益重要。它不仅承载了企业的IT战略&#xff0c;更是确保企业在复杂、动态的市场环境中稳定运行的关键。作为信息系统的骨架&#xff0c;一个精心设…

贪心算法学习二

例题一 解法&#xff08;贪⼼&#xff09;&#xff1a; 贪⼼策略&#xff1a; 由于只能交易⼀次&#xff0c;所以对于某⼀个位置 i &#xff0c;要想获得最⼤利润&#xff0c;仅需知道前⾯所有元素的最⼩ 值。然后在最⼩值的位置「买⼊」股票&#xff0c;在当前位置「卖出」…

Spring进阶技巧:利用AOP提前介入的巧妙实践

Spring框架中的面向切面编程&#xff08;AOP&#xff09;是一种强大的机制&#xff0c;它允许开发者在不修改原有代码的情况下&#xff0c;对程序进行横向切面的功能扩展。AOP提供了一种方式&#xff0c;可以在目标Bean的生命周期早期阶段就实施切面逻辑&#xff0c;这为我们在…

JMS VS AMQP

JMS&#xff08;Java Message Service&#xff09;是一个为Java平台设计的API&#xff0c;主要针对Java开发者提供了一套用于企业级消息服务的标准接口。而AMQP&#xff08;Advanced Message Queuing Protocol&#xff09;是一个应用层协议&#xff0c;它提供了一个开放的、标准…

代码随想录算法训练营第十六天| 找树左下角的值、路径总和、 从中序与后序遍历序列构造二叉树

找树左下角的值 题目链接&#xff1a;找树左下角的值 文档讲解&#xff1a;代码随想录 状态&#xff1a;递归没想到中右左的遍历顺序&#xff0c;迭代想出来了 思路&#xff1a;需要找最大深度&#xff0c;然后使用中右左的遍历顺序找最左节点 题解&#xff1a; int res 0;int…

【RK3568】制作Android11开机动画

Android 开机 logo 分为两种&#xff1a;静态显示和动态显示。静态显示就是循环显示一张图片&#xff1b;动态显示就是以特定帧率顺序显示多张图片 1.准备 android logo 图片 Android logo最好是png格式的&#xff0c;因为同一张图片的情况下&#xff0c;png 格式的比 jpg和b…

【ARM Cache 与 MMU 系列文章 7.6 -- ARMv8 MMU 配置 寄存器使用介绍】

请阅读【ARM Cache 及 MMU/MPU 系列文章专栏导读】 及【嵌入式开发学习必备专栏】 文章目录 MMU 转换控制寄存器 TCR_ELxTCR_ELx 概览TCR_ELx 寄存器字段详解TCR 使用示例Normal MemoryCacheableShareability MMU 内存属性寄存器 MAIR_ELxMAIR_ELx 寄存器结构内存属性字段Devic…