数据结构——二叉树(续集)

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥
♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥
♥♥♥我们一起努力成为更好的自己~♥♥♥
♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥
♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥
✨✨✨✨✨✨个人主页✨✨✨✨✨✨
在上一篇博客我们说到了树的基本概念,以及顺序结构二叉树的实现及运用,我们知道二叉树还可以通过链式结构来实现,这一篇博客带着大家一起继续在二叉树的世界里面遨游~(提前透露一下~这一篇博客更多的是体会递归的暴力美学~)
目录
实现链式结构二叉树
结构
手动创建二叉树
二叉树的遍历
遍历方式
前序遍历
中序遍历
后序遍历
二叉树操作
二叉树结点个数
二叉树叶子结点的个数
二叉树第k层结点个数
二叉树的深度/高度
二叉树查找值为x的结点
二叉树销毁
常见问题
层序遍历
思路
代码
判断完全二叉树
画图分析思路
代码
总代码
BTree.h
BTree.c
test.c
实现链式结构二叉树
既然是链式结构的二叉树,结合我们前面的经验那肯定离不开链表了~首先来看看链式结构二叉树的结构~
结构
用链表来表示⼀棵二叉树,即用链来指示元素之间逻辑关系。 通常的方法是链表中每个结点由三个域组 成, 数据域和左、右指针域 ,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
//定义二叉树结点结构
typedef char BTDataType;//保存的数据类型
typedef struct BinaryTreeNode
{BTDataType data;//保存的数据struct BinaryTreeNode* left;//左孩子结点的地址struct BinaryTreeNode* right;//右孩子结点的地址
}BTNode;
手动创建二叉树

这里呢,我们创建一个比较复杂的二叉树,既然二叉树也是由一个个结点组成的,那么我们创建二叉树就需要创建一个个结点再进行连接起来,我们可以看到,上面的二叉树一共有6个结点,所以我们需要创建6个结点再进行连接~注意:这里我们保存的是字符,所以保存数据类型改为char
代码:
#include"Btree.h"//创建一个结点代码
BTNode* BuyNode(BTDataType x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc fail");exit(1);//创建失败,退出程序}//创建成功node->data = x;node->left = node->right = NULL;//返回创建的结点地址return node;
}
//创建二叉树
BTNode* BTreeCreate()
{BTNode* n1 = BuyNode('A');BTNode* n2 = BuyNode('B');BTNode* n3 = BuyNode('C');BTNode* n4 = BuyNode('D');BTNode* n5 = BuyNode('E');BTNode* n6 = BuyNode('F');//通过逻辑关系连接结点n1->left = n2;n1->right = n3;n2->left = n4;n3->left = n5;n3->right = n6;//返回头结点return n1;
}
void test1()
{//手动创建一个二叉树BTNode* head = BTreeCreate();
}
手动创建的二叉树也就完成了,接下来我们想一想怎么对这个二叉树进行操作呢?每一颗子树的末尾都会走到空,显然,我们这里不能像单链表那样进行遍历,那我们应该怎么样遍历二叉树呢?
二叉树的遍历
遍历方式
二叉树的遍历方式有三种:
前序/中序/后序的递归结构遍历:
1.前序遍历(Preorder Traversal 亦称先序遍历):访问根结点的操作发生在遍历其左右子树之前访问顺序为:根结点、左子树、右子树2.中序遍历(Inorder Traversal):访问根结点的操作发生在遍历其左右子树之中(间)访问顺序为:左子树、根结点、右子树3.后序遍历(Postorder Traversal):访问根结点的操作发生在遍历其左右子树之后访问顺序为:左子树、右子树、根结点
我们可以发现,二叉树不同的遍历方式最大的区别就是访问根结点的顺序不同,最开始访问根结点就是前序遍历,中间访问根结点就是中序遍历,最后访问根结点就是后序遍历

知道了这三种遍历方式,那么我们来看看这个二叉树不同遍历方式下有什么结果呢?
前序遍历:
A B D NULL NULL NULL C E NULL NULL F NULL NULL
中序遍历:
NULL D NULL B NULL A NULL E NULL C NULL F NULL
后序遍历:
NULL NULL D NULL B NULL NULL E NULL NULL F C A
不知道你的答案有没有正确呢?这种遍历有一种方式就是先全局再局部进行遍历,往下走就是子树也按照规定顺序进行遍历就可以了。
比如举中序遍历的例子:
首先我们知道根结点在中间,那么我们首先就可以确定整体的样子,再在子数中操作
(B子树)A(C子树)
(……B……)A (……C……)
(…D…B NULL)A (…E…C…F…)
NULL D NULL B NULL A NULL E NULL C NULL F NULL
最后我们就可以得到中序遍历的结果:NULL D NULL B NULL A NULL E NULL C NULL F NULL
其他的遍历方式操作方法类似,当然也可以画图来理解遍历的过程~
知道了这三种遍历方式,我们怎么用代码实现呢?
这里就要开始我们递归的暴力美学了~我们可以发现二叉树是一层层往下面递进的~相当于有一个递归的过程~
前序遍历
//前序遍历
void PreOrder(BTNode* root)
{if (root == NULL){//如果为空,直接打印返回printf("NULL ");return;}//递归遍历,最开始打印根结点数据printf("%c ", root->data);PreOrder(root->left);PreOrder(root->right);
}
看看打印结果~


我们可以发现和我们前面分析的结果是一模一样的,是不是很神奇~这里我们来看看这里的递归是如何达到我们想要的效果的~
解释:如果根结点为空就打印NULL,如果根结点不为空就打印根结点,然后再同样的方式遍历左孩子结点和右孩子结点(左孩子结点和右孩子结点也就是子树的根结点),这里的递归也就是先往下面一层层递归然后再回退~画图分析~

怎么样~有没有体会到递归的暴力美学呢?有了这一个基础,接下来我们的中序遍历和后序遍历相信就简单了~
中序遍历
//中序遍历
void InOrder(BTNode* root)
{if (root == NULL){//如果为空,直接打印返回printf("NULL ");return;}//根结点在中间打印InOrder(root->left);printf("%c ", root->data);InOrder(root->right);
}


后序遍历
//后序遍历
void LasOrder(BTNode* root)
{if (root == NULL){//如果为空,直接打印返回printf("NULL ");return;}//根结点最后打印LasOrder(root->left);LasOrder(root->right);printf("%c ", root->data);
}


这里三种遍历相信问题不大,递归的魅力有没有体会到呢?接下来我们继续使用递归对二叉树进行操作~
二叉树操作
还是以这一个二叉树为例

二叉树结点个数
这里我们来巧妙的使用递归方法求结点个数~
原理:
总结点个数 = 1 + 左子树结点个数 +右子树结点个数(根结点为空返回0)
代码:
// 二叉树结点个数
int BinaryTreeSize(BTNode* root)
{if (root == NULL){return 0;}return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

二叉树叶子结点的个数
1.什么是叶子结点?
度为0,左孩子结点和右孩子结点都为NULL
2.原理
叶子结点总个数=左子树叶子结点个数 +右子树叶子结点个数
代码:
// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{//root不为空,避免对空指针解引用//左孩子结点和右孩子结点都为NULL是叶子结点if (root != NULL && root->left == NULL && root->right == NULL){return 1;}//如果root为NULL,返回0if (root == NULL){return 0;}//叶子结点总个数=左子树叶子结点个数 +右子树叶子结点个数return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

二叉树第k层结点个数
原理:
二叉树第k层结点个数 = 左子树第k层结点个数 +右子树第k层结点个数
代码:
// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{//如果为空,没有结点,返回0if (root == NULL){return 0;}//root不为空,并且走到第k层//后面传参k-1,到第k层也就是k=1//例:求第三层,从第一层向下走2层就可以到第三层if (k == 1){return 1;}//二叉树第k层结点个数 = 左子树第k层结点个数 +右子树第k层结点个数return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

二叉树的深度/高度
思路:
二叉树的深度/高度 = 1 + Max(左子树深度/高度、右子树深度/高度最大值)
(如果为空返回0)
代码:
// 二叉树的深度/高度
int BinaryTreeDepth(BTNode* root)
{//如果root为空返回0if (root == NULL){return 0;}//求左子树深度/高度int DepLeft = BinaryTreeDepth(root->left);// 求右子树深度/高度int DepRight = BinaryTreeDepth(root->right);//返回1+左子树深度/高度、右子树深度/高度最大值return 1 + (DepLeft > DepRight ? DepLeft : DepRight);
}

二叉树查找值为x的结点
既然需要查找结点,那么这里肯定离不开遍历二叉树了~
思路:
先判断根结点root是否为要找的结点,如果是直接返回root,如果root为空就返回NULL,然后在左子树里面找,最后在右子树里面找,都没有找到就返回NULL。
代码:
// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{//判断根结点if (root == NULL){return NULL;}if (root->data == x){return root;}//在左子树里面找BTNode* leftFind = BinaryTreeFind(root->left, x);//左子树找到结果不为空,返回if (leftFind){return leftFind;}//在右子树里面找BTNode* rightFind = BinaryTreeFind(root->left, x);//右子树找到结果不为空,返回if (rightFind){return rightFind;}//最后没有找到return NULL;
}
测试:


二叉树销毁
接下来就是二叉树的销毁了~
这里有一个需要注意的点是我们前面对二叉树操作并没有更改头结点,但是二叉树销毁,是每一个结点都需要销毁的,所以我们要传二级指针~
思路:遍历二叉树销毁,root为NULL直接返回,这里我们需要使用后序遍历(如果前面就把root置为空之后,我们是不能对空指针进行解引用的~就找不到左右孩子结点了~)
代码:(后序遍历销毁)
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{//为空直接返回,不需要销毁了if (*root == NULL){return;}//后序遍历销毁——左右根//注意:二级指针传地址BinaryTreeDestory(&((*root)->left));BinaryTreeDestory(&((*root)->right));free(*root);*root = NULL;
}

我们可以看到二叉树销毁成功~
常见问题
这里二叉树的大部分操作已经完成了~接下来看一些常见问题~
1.不同函数特殊情况处理操作中为什么有的return NULL;有的return 0;有的直接return;
答:这就与函数的返回值有关了,我们可以看到有的函数返回值是int类型,有的是BTNode*类型,有的是void类型,这里返回的与返回值类型保持一致~
例:
int类型

BTNode*类型

void类型:

层序遍历
前面讲解了二叉树的三种遍历方式——前中后序遍历~除此之外,二叉树还有一种遍历方式也就是层序遍历~听这个名字是不是就是一层层的进行遍历呢?答案是的~
比如下面这个二叉树遍历结果为:A B C D E F

思路
那么我们怎么通过代码来实现这个过程呢?
这里就需要我们前面学到的数据结构知识——队列(不清楚的可以看看前面的文章:数据结构——栈和队列)
这里我们给出思路:
首先让根结点入队列,循环判断队列是否为空,如果队列不为空就取队头并且出队头,如果结点的左右孩子不为空就让结点的左右孩子入队列,如此循环
画图理解:

1.让根结点入队列(队尾入,队头出)

2.判断队列是否为空,队列不为空就取队头并且出队头,然后让结点的左右孩子入队列




当队列为空,这个循环就结束,我们可以看到这就是层序遍历的结果~
注意:
1.这里是结点入队列,所以队列保存的数据类型是struct BinaryTreeNode*!
有人可能会说不是将struct BinaryTreeNode重定义为BTNode了吗?是不是也可以写成
typedef BTNode* QueueDataType,答案是不可以这样写的,我们必须告诉操作系统这一个类型是struct这样一个结构,如果这样使用同样要在前面加上struct。
正确使用:
方法一:
typedef struct BinaryTreeNode* QueueDataType;
方法二:
typedef struct BTNode* QueueDataType;
这样看起来,我们还是推荐使用方法一~
2.在前面,我们已经实现过队列,有相应的头文件和源文件这里我们只需要包含头文件使用就可以了~
代码
知道了思路,接下来就是我们的代码实现了~
// 层序遍历
void LevelOrder(BTNode* root)
{if (root == NULL){return;}//定义一个队列结构Queue Qu;//初始化QueueInit(&Qu);//让根结点入队列QueuePush(&Qu, root);//队列不为空,取队头打印数据,并且出队头while (!QueueEmpty(&Qu)){BTNode* top = QueueFront(&Qu);printf("%c ", top->data);//队头出队列QueuePop(&Qu);//如果左右孩子结点不为空入队列if (top->left){QueuePush(&Qu, top->left);}if (top->right){QueuePush(&Qu, top->right);}}//销毁QueueDestroy(&Qu);
}

我们可以看到达到了我们想要的效果~
如果我们也想要像前面打印NULL,代码就会有一些小变化~打印NULL,那么NULL也需要入队列~如果取队头为NULL,就直接打印,然后使用continue语句继续执行循环~
// 层序遍历2
//为空也入队列
void LevelOrder2(BTNode* root)
{//定义一个队列结构Queue Qu;//初始化QueueInit(&Qu);//让根结点入队列//为空也入队列,不需要判断QueuePush(&Qu, root);//队列不为空,取队头打印数据,并且出队头while (!QueueEmpty(&Qu)){BTNode* top = QueueFront(&Qu);//队头为空打印NULL并且NULL出队列if (top == NULL){printf("NULL ");QueuePop(&Qu);continue;//继续执行循环}else{printf("%c ", top->data);}//队头出队列QueuePop(&Qu);//左右孩子结点入队列QueuePush(&Qu, top->left);QueuePush(&Qu, top->right);}//销毁QueueDestroy(&Qu);
}

这里的层序遍历就巧妙地将二叉树和队列结合在一起~
判断完全二叉树
画图分析思路
前面我们说到完全二叉树的特点是最后一层结点个数不一定达到最大~但是结点从左向右依次排列~
这里我们需要判断一个二叉树是不是完全二叉树应该怎么办呢?这里同样需要使用队列~这里我们来画图找完全二叉树和非完全二叉树的区别~
完全二叉树:

1.根结点入队列

2.队列不为空,取队头出队头,将左右孩子结点入队列(这里结点为空也入,方便后面判断),如此循环,到取到top为NULL第一层循环结束,第二次循环条件依然是队列不为空,取剩下的队列元素



3.第二层循环取队头出队头

我们可以发现完全二叉树剩下的队列元素都是空~
接下来我们看看非完全二叉树进行同样的操作~
1.根结点入队列

2.队列不为空,取队头出队头,将左右孩子结点入队列(这里结点为空也入,方便后面判断),如此循环,到取到top为NULL第一层循环结束,第二次循环条件依然是队列不为空,取剩下的队列元素




3.第二层循环取队头出队头

我们可以看到非完全二叉树第二次循环取队头元素有不为空的结点~这就是它们之间的区别~
思路:首先让根结点入队列,第一次循环队列不为空,取队头出队头,将左右孩子结点入队列(这里结点为空也入,方便后面判断),如此循环,到取到top为NULL第一层循环结束,第二次循环条件依然是队列不为空,取剩下的队列元素,剩下的队列元素有不为空的说明是非完全二叉树~
代码
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{//借助队列Queue Qu;//初始化QueueInit(&Qu);//根结点入队列QueuePush(&Qu, root);while (!QueueEmpty(&Qu)){//取队头元素BTNode* top = QueueFront(&Qu);//出队列QueuePop(&Qu);//为空第一层循环结束if (top == NULL){break;}//让左右孩子结点入队列QueuePush(&Qu, top->left);QueuePush(&Qu, top->right);}//第二层循环,队列不为空//继续取队头出队头,如果有不为NULL的说明是非完全二叉树while (!QueueEmpty(&Qu)){//取队头元素BTNode* top = QueueFront(&Qu);//出队列QueuePop(&Qu);if (top != NULL){//返回前销毁队列!!!QueueDestroy(&Qu);return false;}}//剩下的全部为NULL,是完全二叉树return true;//销毁QueueDestroy(&Qu);
}

我们的代码达到了我们想要的效果~
总代码
BTree.h
//需要的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include"Queue.h"//定义二叉树结点结构
typedef char BTDataType;//保存的数据类型
typedef struct BinaryTreeNode
{BTDataType data;//保存的数据struct BinaryTreeNode* left;//左孩子结点的地址struct BinaryTreeNode* right;//右孩子结点的地址
}BTNode;//前序遍历
void PreOrder(BTNode* root);//中序遍历
void InOrder(BTNode* root);//后序遍历
void LasOrder(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, BTDataType x);// 二叉树销毁
void BinaryTreeDestory(BTNode** root);// 层序遍历1
void LevelOrder1(BTNode* root);
// 层序遍历2
void LevelOrder2(BTNode* root);// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);
BTree.c
#include"Btree.h"//前序遍历
void PreOrder(BTNode* root)
{if (root == NULL){//如果为空,直接打印返回printf("NULL ");return;}//递归遍历,最开始打印根结点数据printf("%c ", root->data);PreOrder(root->left);PreOrder(root->right);
}//中序遍历
void InOrder(BTNode* root)
{if (root == NULL){//如果为空,直接打印返回printf("NULL ");return;}//根结点在中间打印InOrder(root->left);printf("%c ", root->data);InOrder(root->right);
}//后序遍历
void LasOrder(BTNode* root)
{if (root == NULL){//如果为空,直接打印返回printf("NULL ");return;}//根结点最后打印LasOrder(root->left);LasOrder(root->right);printf("%c ", root->data);
}// 二叉树结点个数
int BinaryTreeSize(BTNode* root)
{if (root == NULL){return 0;}return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{//root不为空,避免对空指针解引用//左孩子结点和右孩子结点都为NULL是叶子结点if (root != NULL && root->left == NULL && root->right == NULL){return 1;}//如果root为NULL,返回0if (root == NULL){return 0;}//叶子结点总个数=左子树叶子结点个数 +右子树叶子结点个数return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{//如果为空,没有结点,返回0if (root == NULL){return 0;}//root不为空,并且走到第k层//后面传参k-1,到第k层也就是k=1//例:求第三层,从第一层向下走2层就可以到第三层if (k == 1){return 1;}//二叉树第k层结点个数 = 左子树第k层结点个数 +右子树第k层结点个数return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}// 二叉树的深度/高度
int BinaryTreeDepth(BTNode* root)
{//如果root为空返回0if (root == NULL){return 0;}//求左子树深度/高度int DepLeft = BinaryTreeDepth(root->left);// 求右子树深度/高度int DepRight = BinaryTreeDepth(root->right);//返回1+左子树深度/高度、右子树深度/高度最大值return 1 + (DepLeft > DepRight ? DepLeft : DepRight);
}// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{//判断根结点if (root == NULL){return NULL;}if (root->data == x){return root;}//在左子树里面找BTNode* leftFind = BinaryTreeFind(root->left, x);//左子树找到结果不为空,返回if (leftFind){return leftFind;}//在右子树里面找BTNode* rightFind = BinaryTreeFind(root->left, x);//右子树找到结果不为空,返回if (rightFind){return rightFind;}//最后没有找到return NULL;
}// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{//为空直接返回,不需要销毁了if (*root == NULL){return;}//后序遍历销毁——左右根//注意:二级指针传地址BinaryTreeDestory(&((*root)->left));BinaryTreeDestory(&((*root)->right));free(*root);*root = NULL;//中序遍历销毁//err/*BinaryTreeDestory(&((*root)->left));free(*root);*root = NULL;BinaryTreeDestory(&((*root)->right));*/
}// 层序遍历
void LevelOrder1(BTNode* root)
{if (root == NULL){return;}//定义一个队列结构Queue Qu;//初始化QueueInit(&Qu);//让根结点入队列QueuePush(&Qu, root);//队列不为空,取队头打印数据,并且出队头while (!QueueEmpty(&Qu)){BTNode* top = QueueFront(&Qu);printf("%c ", top->data);//队头出队列QueuePop(&Qu);//如果左右孩子结点不为空入队列if (top->left){QueuePush(&Qu, top->left);}if (top->right){QueuePush(&Qu, top->right);}}//销毁QueueDestroy(&Qu);
}// 层序遍历2
//为空也入队列
void LevelOrder2(BTNode* root)
{//定义一个队列结构Queue Qu;//初始化QueueInit(&Qu);//让根结点入队列//为空也入队列,不需要判断QueuePush(&Qu, root);//队列不为空,取队头打印数据,并且出队头while (!QueueEmpty(&Qu)){BTNode* top = QueueFront(&Qu);//队头为空打印NULL并且NULL出队列if (top == NULL){printf("NULL ");QueuePop(&Qu);continue;//继续执行循环}else{printf("%c ", top->data);}//队头出队列QueuePop(&Qu);//左右孩子结点入队列QueuePush(&Qu, top->left);QueuePush(&Qu, top->right);}//销毁QueueDestroy(&Qu);
}// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{//借助队列Queue Qu;//初始化QueueInit(&Qu);//根结点入队列QueuePush(&Qu, root);while (!QueueEmpty(&Qu)){//取队头元素BTNode* top = QueueFront(&Qu);//出队列QueuePop(&Qu);//为空第一层循环结束if (top == NULL){break;}//让左右孩子结点入队列QueuePush(&Qu, top->left);QueuePush(&Qu, top->right);}//第二层循环,队列不为空//继续取队头出队头,如果有不为NULL的说明是非完全二叉树while (!QueueEmpty(&Qu)){//取队头元素BTNode* top = QueueFront(&Qu);//出队列QueuePop(&Qu);if (top != NULL){//返回前销毁队列!!!QueueDestroy(&Qu);return false;}}//剩下的全部为NULL,是完全二叉树return true;//销毁QueueDestroy(&Qu);
}
test.c
#include"Btree.h"//创建一个结点代码
BTNode* BuyNode(BTDataType x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc fail");exit(1);//创建失败,退出程序}//创建成功node->data = x;node->left = node->right = NULL;//返回创建的结点地址return node;
}
//创建二叉树
BTNode* BTreeCreate()
{BTNode* n1 = BuyNode('A');BTNode* n2 = BuyNode('B');BTNode* n3 = BuyNode('C');BTNode* n4 = BuyNode('D');BTNode* n5 = BuyNode('E');BTNode* n6 = BuyNode('F');//通过逻辑关系连接结点n1->left = n2;n1->right = n3;n2->left = n4;n3->left = n5;n3->right = n6;//返回头结点return n1;
}
void test1()
{//手动创建一个二叉树BTNode* head = BTreeCreate();//前序遍历printf("前序遍历:");PreOrder(head);printf("\n");//中序遍历printf("中序遍历:");InOrder(head);printf("\n");//后序遍历printf("后序遍历:");LasOrder(head);printf("\n");
}void test2()
{//手动创建一个二叉树BTNode* head = BTreeCreate();//二叉树结点个数printf("二叉树结点个数:%d\n", BinaryTreeSize(head));printf("二叉树叶子结点个数:%d\n", BinaryTreeLeafSize(head));/*printf("二叉树第%d层结点个数:%d\n", 1, BinaryTreeLevelKSize(head, 1));printf("二叉树第%d层结点个数:%d\n", 3, BinaryTreeLevelKSize(head, 3));printf("二叉树第%d层结点个数:%d\n", 4, BinaryTreeLevelKSize(head, 4));*/printf("二叉树的深度/高度:%d\n", BinaryTreeDepth(head));/*BTNode* find = BinaryTreeFind(head, 'B');if (find){printf("找到了!\n");}else{printf("没有找到!\n");}*///二级指针传地址//BinaryTreeDestory(&head);
}void test3()
{//手动创建一个二叉树BTNode* head = BTreeCreate();//层序遍历printf("LevelOrder1:\n");LevelOrder1(head);printf("\n");printf("LevelOrder2:\n");LevelOrder2(head);//判断完全二叉树/*if (BinaryTreeComplete(head)){printf("是完全二叉树!\n");}else{printf("不是完全二叉树!\n");}*/
}int main()
{//test1();//test2();test3();return 0;
}
♥♥♥本篇博客内容结束,期待与各位未来的优秀程序员交流,有什么问题请私信♥♥♥
♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥
相关文章:
数据结构——二叉树(续集)
♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨个人…...
ElasticSearch学习篇16_《检索技术核心20讲》进阶篇之空间检索
背景 学习极客实践课程《检索技术核心20讲》https://time.geekbang.org/column/article/215243,文档形式记录笔记。 相关问题: 查询范围固定的需求 直接计算两点之间距离区域二进制编码GeoHash编码 查询范围不固定的需求 GeoHash编码索引结构设计 基于…...
uni-app跨域set-cookie
set-cookie的值是作为一个权限控制的 首先,无论什么接口都会返回一个set-cookie,但未登录时,set-cookie是没有任何权限的 其次,登录接口请求时会修改set-cookie,并且在后续其他接口发起请求时,会在请求头…...
移动应用开发:简易登录页
文章目录 简介一,创建新活动二,设计UI布局三,编写活动代码四,运行应用程序注意 简介 使用Android Studio编写的简单Android 登录应用程序,该应用程序包含一个登录界面,具有账号和密码两个文本框࿰…...
C++_ C++11的override和final
文章目录 1. override 关键字2. final 关键字在虚函数上使用 final在类上使用 final 1. override 关键字 用于明确表示派生类中的某个虚函数是用来重写基类中的虚函数的,这样编译器会检查基类,看看是否确实存在同样的虚函数,如果没有匹配&am…...
【MyBatis源码】SQL 语句构建器AbstractSQL
文章目录 介绍org.apache.ibatis.jdbc.SQLSQL类使用示例SelectProvider搭配动态SQLAbstractSQL类源码分析 介绍 当我们需要使用Statement对象执行SQL时,SQL语句会嵌入Java代码中。SQL语句比较复杂时,我们可能会在代码中对SQL语句进行拼接,查…...
C++OJ_二叉树的层序遍历
✨✨ 欢迎大家来到小伞的大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:C_OJ 小伞的主页:xiaosan_blog 二叉树的层序遍历 102. 二叉树的层序遍历 - 力扣(LeetCode࿰…...
什么是直方图算法
什么是直方图算法? 直方图算法是一种优化决策树分裂点搜索效率的算法,被广泛应用于像 LightGBM 和 XGBoost 这样的梯度提升决策树框架中。其核心思想是通过将连续特征的取值范围离散化为有限的区间(称为 bins),在这些…...
pg_dump -Fc 导出的自定义格式数据库文件 相关操作
实例 将 test.dmp 文件转换为普通SQL内容, 并打印到屏幕 pg_restore -U postgres -Fc -f - test.dump将 test.dmp 文件转换为普通SQL内容, 并输出到 test.sql 文件中 pg_restore -U postgres -Fc -f test.sql -v test.dump备份得到自定义格式的数据库文件(dmp) pg_dump -U…...
Oh My Posh安装
nullSet up your terminalhttps://ohmyposh.dev/docs/installation/windows Git ee oh-my-posh: Windows上的oh-my-zsh,源地址 https://github.com/JanDeDobbeleer/oh-my-posh.git (gitee.com)https://gitee.com/efluent/oh-my-posh...
Node.js——fs模块-文件夹操作
1、借助Node.js的能力,我们可以对文件夹进行创建、读取、删除等操作 2、方法 方法 说明 mkdir/mkdirSync 创建文件夹 readdir/readdirSync 读取文件夹 rmdir/rmdirSync 删除文件夹 3、语法 其余的方法语法类似 本文的分享到此结束,欢迎大家评论区…...
15分钟学 Go 实战项目三 : 实时聊天室(学习WebSocket并发处理)
实时聊天室:学习WebSocket并发处理 目标概述 在本项目中,我们将创建一个实时聊天室,使用Go语言和WebSocket来处理并发消息交流。这将帮助你深入理解WebSocket协议的工作原理以及如何在Go中实现并发处理。 1. 项目需求 功能需求 用户可以…...
架构评估的方法
三种评估方法※ 第一是基于问卷(检查表)的方式,通过问卷调查对系统比较熟悉的相关人员,这种方式主观性很强。 专家问卷评估、用户问卷评估、内部团队问卷评估 第二是基于度量的方式,对系统指标完全量化,基于量化指标评价系统,这种方式需要评估者对系统非常熟悉。 软件质…...
羲和数据集收集器1.0
为了提升问答对的提取能力并完善GUI,我们从以下几个方面进行改进: 增强文本清理和解析能力:确保能够更准确地识别问答对。 支持更多文件格式:除了现有的 .txt, .docx, 和 .pdf,可以考虑支持其他常见格式如 .xlsx 等。 优化GUI设计:提供更友好的用户界面,包括进度条、日…...
ENSP OSPF和BGP引入
路由协议分为:内部网关协议和外部网关协议。内部网关协议用于自治系统内部的路由,包括:RIP和OSPF。外部网关协议用于自治系统之间的路由,包括BGP。内部网关协议和外部网关协议配合来共同完成网络的路由。 BGP:边界网关路由协议(b…...
软件工程 软考
开发大型软件系统适用螺旋模型或者RUP模型 螺旋模型强调了风险分析,特别适用于庞大而复杂的、高风险的管理信息系统的开发。喷泉模型是一种以用户需求为动力,以对象为为驱动的模型,主要用于描述面向对象的软件开发过程。该模型的各个阶段没有…...
证书学习(六)TSA 时间戳服务器原理 + 7 个免费时间戳服务器地址
目录 一、简介1.1 什么是时间戳服务器1.2 名词扩展1.3 用时间戳标记顺序1.4 7 个免费TSA时间戳服务器地址(亲测可用)1.5 RFC 3161 标准二、时间戳原理2.1 时间戳服务工作流程2.2 验证工作流程2.3 举个例子2.4 时间戳原理总结三、代码实现3.1 curl 命令请求时间戳3.2 java 代码…...
NVR设备ONVIF接入平台EasyCVR私有化部署视频平台如何安装欧拉OpenEuler 20.3 MySQL
在当今数字化时代,安防视频监控系统已成为保障公共安全和个人财产安全的重要工具。NVR设备ONVIF接入平台EasyCVR作为一款功能强大的智能视频监控管理平台,它不仅提供了视频远程监控、录像、存储与回放等基础功能,还涵盖了视频转码、视频快照、…...
c中柔性数组
c99中,结构中最后一个元素允许是未知大小的数组,这就叫柔性数组成员。 柔性数组的特点 1.结构中柔性数组前必须至少有一个其他成员 2.sizeof返回的这种结构大小不包括柔性数组的内存 3.包含柔性数组成员的结构用malloc函数进行动态分配,并…...
图像信号处理器(ISP,Image Signal Processor)详解
简介:个人学习分享,如有错误,欢迎批评指正。 图像信号处理器(ISP,Image Signal Processor) 是专门用于处理图像信号的硬件或处理单元,广泛应用于图像传感器(如 CMOS 或 CCD 传感器&a…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...


