【数据结构】二叉树初阶——超详解!!!(包含二叉树的实现)

【数据结构】二叉树初阶——超详解!!!(包含二叉树的实现)

【数据结构】二叉树初阶——超详解!!!(包含二叉树的实现)

前言

往期我们的学习中讲到了顺序表、链表、栈以及队列等数据结构
它们可以帮我们解决很多问题,而类似的数据结构还有很多
今天,我们就来聊聊——二叉树

一、树是什么?

1. 树的定义

树:
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。
有一个特殊的结点,称为根结点,根结点没有前驱结点
除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继

2. 一些常见术语

这就是一个常见的树:

在这里插入图片描述
以下还有一些常见的术语:(如图)
结点的度:一个结点含有的子树的个数称为该结点的度;(A的度为6)
叶结点或终端结点:度为0的结点称为叶结点;(B、H、P......为叶结点)
双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点;(A为B的父结点)
子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点;(B为A的子结点)
树的高度或深度:树中结点的最大层次;(该树高度为4)

二、二叉树

1. 二叉树是什么 ?

二叉树 就是一种特殊的树,其树的度最大为 2
也就是说,一个结点最多有两个分叉
而在日常生活中,由于树的结构复杂很少用,所以最实用的是二叉树,其只有最多两个分叉

2. 二叉树的组成

二叉树由一个根结点加上两棵别称为左子树和右子树的二叉树组成
且注意二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

如图,就是一个二叉树:

在这里插入图片描述


其组成就是:

在这里插入图片描述

2. 特殊的二叉树

二叉树有两种特殊情况:
满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树

也就是说,如果一个二叉树的层数为 K ,且结点总数是 2 ^ k - 1 ,则它就是满二叉树。
完全二叉树:对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1 至 n的结点一一对应时称之为完全二叉树
要注意的是满二叉树是一种特殊的完全二叉树
完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的

如下图,分别是满二叉树和完全二叉树:

在这里插入图片描述


要注意,完全二叉树的编号是连续的,中间断开则不是完全二叉树
如下图的树就不是完全二叉树:

在这里插入图片描述

3. 二叉树的顺序存储(完全二叉树)

对于一个完全二叉树的存储,前面也提到完全二叉树的编号是连续的
所以,对于完全二叉树来说,我们可以用数组来进行存储,用下标来进行编号
二叉树顺序存储在物理上是一个数组,而在逻辑上是一颗二叉树

如图,一个逻辑结构(我们人为想象的),一个物理结构(真实存储的):

在这里插入图片描述


在这里插入图片描述

4. 二叉树的一些性质

1. 若规定根结点的层数为 1,则一棵非空二叉树的第 i 层上最多有 2 ^ ( k - 1 ) 个结点
2
. 若规定根结点的层数为 1 ,则深度为 h 的二叉树的最大结点数是 2 ^ h - 1
3
. 对任何一棵二叉树, 如果度为 0 其叶结点个数为 n₀ , 度为 2 的分支结点个数为 n₂ ,则有 n₀ = n₂ + 1
4
. 若规定根结点的层数为 1,具有n个结点的满二叉树的深度,h = log₂( n + 1 )

最后一点,也是最重要的一点:

对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号
(前面提到过)
存在:若父亲在数组中下标为i,则该父亲的左孩子下标为 2 * i + 1;左孩子下标为2 * i + 2若孩子在数组中下标为i,则该孩子的父亲下标为( i - 1)/ 2
注意:前提是这些小标所对应的值存在!!!

以上的性质都是一些数学的基本常识,小编在这里也不过多的介绍
感兴趣的可以自己推导推导哦

三、二叉树的实现(重点!!!)

1. 二叉树的链式存储(非完全二叉树)

前面我们说到存储完全二叉树可以用数组来进行,是因为完全二叉树的编号是连续的
而非完全二叉树却不是如此,若强行用数组来存储会浪费大量的空间

如图:

在这里插入图片描述


那么除了数组,我们还可以用什么来储存二叉树呢?
我们会想到链式存储

链式存储
故名思意,就是用链表来表示一颗二叉树,用链来指示元素的逻辑关系
通常的方法
创建一链表,使每个结点由三个域组成,数据域和左右指针域
左右指针
分别用来给出该结点左孩子和右孩子所在结点的地址
(下面会提到)

2. 实现思路

知道了用链式存储,现在就可以开始实现代码了

与单链表的实现类似,二叉树中的每一个节点有以下三个成员:
1
. 节点中存储的数据
2
. 指向节点左孩子的指针
3
. 指向节点右孩子的指针

3. 代码实现

本文以创建一个 char 类型的二叉树为例

(1)创建头文件&源文件

之前在讲解扫雷游戏中我就提到:
在写复杂程序时要养成写多个头文件&源文件的好习惯,这样条理就很清晰也不会乱

详见【C语言】扫雷游戏详解(包含递归,变色,记录时间等)

在这里插入图片描述

如图:
创建了一个 “ BTNode.h "头文件
用于存放用来放函数的声明和一些库函数的头文件

创建了一个 “BTNode.c "源文件
用于用来放函数的定义(二叉树的主体)

还有一个 ” Test.c "源文件
用于测试实现的二叉树的运行效果

(其他的后面会说)

(2)定义二叉树(定义)

首先我们要定义一个二叉树
这里之前讲过,创建一个类似链表的结构
每个结点里面包含三个成员:

节点中存储的数据
指向左孩子的指针
指向右孩子的指针

代码演示:(内有注释)
(在头文件“ BTNode.h "中写)

//重定义,方便修改类型typedefchar BTDataType;//表示每个节点的类型typedefstructBinaryTreeNode{ BTDataType data;//节点中存储的数据structBinaryTreeNode* left;//指向左孩子的指针structBinaryTreeNode* right;//指向右孩子的指针}BTNode;
在定义二叉树的代码中,有两个需要注意的点:本文是以 char 类型为例,但如果以后要将二叉树中的元素类型修改成 int 类型或是其他类型一个一个修改就很麻烦
所以我们重定义char类型为BTDataType,并将接下来代码中的char类型全部写成BTDataType
这是为了方便我们以后对类型进行修改,仅需将char改为其他类型即可
在定义二叉树的同时重定义二叉树的变量名为 BTNode方便以后使用

(3)构建二叉树

小编这里通过一个前序遍历的数组"ABD##E#H##CF##G##"来构建了二叉树
要先有一个二叉树,才能对二叉树进行操作

(关于怎么构建的不重要,大家也可以将一个一个结点进行插入
但这不是重点!!!重点是之后二叉树的实现!!!)

代码演示:

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 BTNode*BinaryTreeCreate(BTDataType* ch,int* pi){if(ch[*pi]=='#'){returnNULL;} BTNode* newnode =(BTNode*)malloc(sizeof(BTNode));if(newnode ==NULL){perror("malloc fail");return;} newnode->data = ch[(*pi)++]; newnode->left =BinaryTreeCreate(ch, pi);(*pi)++; newnode->right =BinaryTreeCreate(ch, pi);return newnode;}

(4)二叉树遍历(前中后序)

要想对二叉树进行操作,肯定少不了遍历二叉树
但是要怎么遍历是个问题,这里一共有4种:
前序遍历、中序遍历、后序遍历、层序遍历
这四种遍历主要在于遍历顺序的不一样,我们一一来拆解
  • 前序
前序遍历是指遍历时先遍历根、接着左子树、最后右子树

还要注意,这个遍历是递归进行的!!!

当遍历完根后,开始遍历左子树,就以左子树为起始位置,继续从根遍历、接着左子树、最后右子树,以此循环,直到全部遍历结束

代码演示:
(内有注释,自行查看)

// 二叉树前序遍历 voidBinaryTreePrevOrder(BTNode* root){//当遍历到空就返回if(root ==NULL){return;}//打印根,然后递归遍历左子树、右子树printf("%c ", root->data);BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);}
  • 中序
中序遍历原理与前序遍历一样,只是遍历的顺序不一样
中序遍历是指遍历时先遍历左子树、接着根、最后右子树

代码演示:
(内有注释,自行查看)

// 二叉树中序遍历voidBinaryTreeInOrder(BTNode* root){//当遍历到空就返回if(root ==NULL){return;}//递归遍历左子树、打印根、递归遍历右子树BinaryTreeInOrder(root->left);printf("%c ", root->data);BinaryTreeInOrder(root->right);}
  • 后序
后序遍历原理与前序遍历也一样,只是遍历的顺序不一样
后序遍历是指遍历时先遍历右子树、接着左子树、最后根

代码演示:
(内有注释,自行查看)

// 二叉树后序遍历voidBinaryTreePostOrder(BTNode* root){//当遍历到空就返回if(root ==NULL){return;}//递归遍历左子树、递归遍历右子树、打印根BinaryTreePostOrder(root->left);BinaryTreePostOrder(root->right);printf("%c ", root->data);}

(5)二叉树的层序遍历

这里看到小编特意把层序遍历与前三个遍历分开写,也可以看出层序的不一样了
也确实,层序遍历与前三种遍历差了很多

首先,层序遍历是什么?
层序层序,就是一层一层的遍历

比如下图的二叉树:

该二叉树层序遍历的结果就是【1,2,3,4,5,6,7】
也就是一层一层遍历,这就是层序遍历
那又要该怎么写代码呢?
接着,代码思路是什么?
我们怎么做到遍历完 1,2,3 后去遍历 4,5 呢?
此时也无法直接通过 2 来找到 4,5 了
这时我们可以想到我们之前学过的东西——队列
我们可以在每次取出结点的同时,将该结点的左结点和右结点存储进队列中,直到队列为空
例如:
开始时放入1,此时队列【1】
取出1,并放入2、3,此时队列【2,3】
取出2,并放入4、5,此时队列【3,4,5】
取出3,并放入6、7,此时队列【4,5,6,7】
取出4,此时队列【5,6,7】
取出5,此时队列【6,7】
取出6,此时队列【7】
取出7,此时队列【】

在前面的学习中小编已经讲解过了队列了,这里就不赘述,想知道的可以去看看
链接:
【数据结构】队列——超详解!!!(包含队列的实现)

最后,代码实现
现在,知道了要用队列来实现,就可以开始来写代码了
我们创建一个队列,先把第一个根节点存入
接着在每次取出结点的同时,将该结点的左结点和右结点存储进队列中
以此循环直到队列为空

代码演示:
(不包含队列的实现)
(内有注释,自行查看)

// 层序遍历voidBinaryTreeLevelOrder(BTNode* root){//先创建一个队列来存储结点 Queue Q;QueueInit(&Q);//先将第一个根结点存入队列中if(root){QueuePush(&Q, root);}//若队列不为空就循环while(!QueueEmpty(&Q)){//接受队头的数据,并删除队头的结点 BTNode* root =QueueFront(&Q);printf("%c ", root->data);QueuePop(&Q);//将队头的左子树、右子树存入队列中(前提是不为空)if(root->left){QueuePush(&Q, root->left);}if(root->right){QueuePush(&Q, root->right);}}//最后不要忘记了销毁队列QueueDestroy(&Q);}

(6)二叉树节点个数的计算

当我们想要用代码来计数二叉树中的所有节点个数,该怎么办呢?
其实这很简单,只需要熟悉递归就行了
当根节点为空时,就计 0
而当根节点为非空时,就 + 1 并递归计算其左右子树再

代码演示:
(内有注释,自行查看)

// 二叉树节点个数intBinaryTreeSize(BTNode* root){return root ==NULL?0:BinaryTreeSize(root->left)+BinaryTreeSize(root->right)+1;//递归左右子树并+1}

(7)二叉树高度的计算

当我们想要用代码来计数二叉树的高度,该怎么办呢?
首先我们要知道二叉树高度是什么,是树中结点的最大层次
而二叉树有左子树以及右子树,故左右子树高度更高的一边 + 1 就是树的高度
所以,我们可以用递归来实现
当根节点为空时,返回 0
而当根节点为非空时,就返回左右子树高度更高的一边并 + 1(根节点本身)

代码演示:
(内有注释,自行查看)

// 二叉树高度intBinaryTreeHight(BTNode* root){//当根节点为空时,返回 0if(root ==NULL){return0;}returnfmax(BinaryTreeHight(root->left),BinaryTreeHight(root->right))+1;//而当根节点为非空时//返回左右子树高度更高的一边并 + 1(根节点本身)}

(8)二叉树第k层节点个数的计算

当我们想要用代码来计数二叉树第k层的节点个数,该怎么办呢?
对于一个满二叉树很简单,套公式就行,但非完全二叉树咋办呢?
这里依旧是递归:
初始 k,若该层不是目标层次,就 k - 1 并递归左右子树
当根节点为空时,返回 0
当根节点为非空且此时 k == 1 时,返回 1(已经到达第 k 层)

代码演示:
(内有注释,自行查看)

// 二叉树第k层节点个数intBinaryTreeLevelKSize(BTNode* root,int k){//当根节点为空时,返回 0if(root ==NULL){return0;}//当根节点为非空且此时 k == 1 时,返回 1(已经到达第 k 层)if(k ==1){return1;}returnBinaryTreeLevelKSize(root->left, k -1)+BinaryTreeLevelKSize(root->right, k -1);//若该层不是目标层次,就 k - 1 并递归左右子树 }

(9)二叉树查找值为x的节点

当我们想要用代码来查找值为x的节点,该怎么办呢?
这里依旧是递归:
当根节点为空时,返回空
当根节点的值为X时,返回根节点的地址
最后找左右子树
但要注意!!!:
该函数返回的是地址,故在递归的过程中地址信息不能丢失,要不断返回
可以先判断返回值是否为真,再决定继续递归还是返回数据

代码演示:
(内有注释,自行查看)

// 二叉树查找值为x的节点 BTNode*BinaryTreeFind(BTNode* root, BTDataType x){if(root ==NULL)//没找到{returnNULL;}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;}}

(10)判断二叉树是否是完全二叉树

现在,给你一个树,怎么用代码来判断该树是完全二叉树呢?
我们可以用到之前的层序遍历来完成
当层序遍历到空结点时,若之后还有数据则不是完全二叉树
当层序遍历到空结点时,若没有数据则是完全二叉树

原理就是层序遍历,只不过多加入了一层判断

代码演示:
(内有注释,自行查看)

// 判断二叉树是否是完全二叉树intBinaryTreeComplete(BTNode* root){//先创建一个队列来存储结点 Queue Q;QueueInit(&Q);//先将第一个根结点存入队列中if(root){QueuePush(&Q, root);}//若队列不为空就循环while(!QueueEmpty(&Q)){//接受队头的数据,并删除队头的结点 BTNode* root =QueueFront(&Q);QueuePop(&Q);//判断是否遇到空结点,遇到就跳出循环if(root ==NULL){break;}//将队头的左子树、右子树存入队列中QueuePush(&Q, root->left);QueuePush(&Q, root->right);}//继续循环判断是否有残余结点while(!QueueEmpty(&Q)){//接受队头的数据,并删除队头的结点 BTNode* root =QueueFront(&Q);QueuePop(&Q);//判断是否遇到非空结点,遇到则说明,结点不连续,不是完全二叉树if(root !=NULL){//最后不要忘记了销毁队列QueueDestroy(&Q);//返回 0(假)return0;}}//最后不要忘记了销毁队列QueueDestroy(&Q);//最后返回 1(真)return1;}

四、二叉树实现完整代码

(包含队列的实现)

1. Queue.h

用于存放用来放函数的声明和一些库函数的头文件

#pragmaonce#define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>#include<stdlib.h>#include<stdbool.h>#include<assert.h>#include<sperror.h>//重定义,方便修改类型typedefint QDataType;//表示每个节点的类型typedefstructQListNode{ QDataType data;//节点中存储的数据structQListNode* next;//指向下一个节点的指针}QNode;// 队列的结构 typedefstructQueue{ QNode* front;//头指针 QNode* tail;//尾指针int size;//总元素个数}Queue;// 初始化队列 voidQueueInit(Queue* q);// 队尾入队列 voidQueuePush(Queue* q, QDataType data);// 队头出队列 voidQueuePop(Queue* q);// 获取队列头部元素  QDataType QueueFront(Queue* q);// 获取队列队尾元素  QDataType QueueBack(Queue* q);// 获取队列中有效元素个数 intQueueSize(Queue* q);// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 intQueueEmpty(Queue* q);// 销毁队列 voidQueueDestroy(Queue* q);

2. Queue.c

用于用来放函数的定义(队列的主体)

#include"Queue .h"// 初始化队列 voidQueueInit(Queue* q){assert(q);//断言空指针 q->front =NULL; q->tail =NULL; q->size =0;//全部初始化置 0 / NULL}// 队尾入队列 voidQueuePush(Queue* q, QDataType data){assert(q);//断言空指针 QNode* tmp =(QNode*)malloc(sizeof(QNode));//直接开辟一个节点的空间if(tmp ==NULL)//加一个 if语句 防止增容失败{perror("malloc");return;}//没有问题后就赋值 tmp->data = data; tmp->next =NULL;//注意:当队列中没有节点时//此时插入的节点既是头节点,又是尾节点if(q->size ==0){ q->front = tmp; q->tail = tmp;}else{ q->tail->next = tmp; q->tail = tmp;} q->size++;}// 队头出队列 voidQueuePop(Queue* q){assert(q);assert(q->size >0);//断言空指针//断言顺序表不能为空//注意:当队列中只有一个节点时,头尾节点相等//此时将头节点和尾节点都释放if(q->size ==1){free(q->front); q->front =NULL; q->tail =NULL;}else{ QNode* del = q->front; q->front = q->front->next;free(del);} q->size--;}// 获取队列头部元素  QDataType QueueFront(Queue* q){assert(q);assert(q->size >0);//断言空指针//断言顺序表不能为空return q->front->data;}// 获取队列队尾元素  QDataType QueueBack(Queue* q){assert(q);assert(q->size >0);//断言空指针//断言顺序表不能为空return q->tail->data;}// 获取队列中有效元素个数 intQueueSize(Queue* q){assert(q);//断言空指针return q->size;}// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 intQueueEmpty(Queue* q){assert(q);//断言空指针return q->size ==0;}// 销毁队列 voidQueueDestroy(Queue* q){assert(q);//断言空指针 QNode* cur = q->front;//遍历链表,一一释放空间销毁while(cur){ QNode* next = cur->next;free(cur); cur = next;} q->front = q->tail =NULL; q->size =0;//全部初始化置 0 / NULL}

3. BTNode.h

用于存放用来放函数的声明和一些库函数的头文件

#pragmaonce#include"Queue .h"typedefchar BTDataType;typedefstructBinaryTreeNode{ BTDataType data;structBinaryTreeNode* left;structBinaryTreeNode* right;}BTNode;// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 BTNode*BinaryTreeCreate(BTDataType* ch,int* pi);// 二叉树前序遍历 voidBinaryTreePrevOrder(BTNode* root);// 二叉树中序遍历voidBinaryTreeInOrder(BTNode* root);// 二叉树后序遍历voidBinaryTreePostOrder(BTNode* root);// 二叉树节点个数intBinaryTreeSize(BTNode* root);// 二叉树叶子节点个数intBinaryTreeLeafSize(BTNode* root);// 二叉树高度intBinaryTreeHight(BTNode * root);// 二叉树销毁voidBinaryTreeDestory(BTNode** root);// 二叉树第k层节点个数intBinaryTreeLevelKSize(BTNode* root,int k);// 二叉树查找值为x的节点 BTNode*BinaryTreeFind(BTNode* root, BTDataType x);// 判断二叉树是否是完全二叉树intBinaryTreeComplete(BTNode* root);// 层序遍历voidBinaryTreeLevelOrder(BTNode* root);

4. BTNode.c

用于用来放函数的定义(二叉树的主体)

#include"BTNode.h"// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 BTNode*BinaryTreeCreate(BTDataType* ch,int* pi){if(ch[*pi]=='#'){returnNULL;} BTNode* newnode =(BTNode*)malloc(sizeof(BTNode));if(newnode ==NULL){perror("malloc fail");return;} newnode->data = ch[(*pi)++]; newnode->left =BinaryTreeCreate(ch, pi);(*pi)++; newnode->right =BinaryTreeCreate(ch, pi);return newnode;}// 二叉树前序遍历 voidBinaryTreePrevOrder(BTNode* root){if(root ==NULL){return;}printf("%c ", root->data);BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);}// 二叉树中序遍历voidBinaryTreeInOrder(BTNode* root){if(root ==NULL){return;}BinaryTreeInOrder(root->left);printf("%c ", root->data);BinaryTreeInOrder(root->right);}// 二叉树后序遍历voidBinaryTreePostOrder(BTNode* root){if(root ==NULL){return;}BinaryTreePostOrder(root->left);BinaryTreePostOrder(root->right);printf("%c ", root->data);}// 层序遍历voidBinaryTreeLevelOrder(BTNode* root){//先创建一个队列来存储结点 Queue Q;QueueInit(&Q);//先将第一个根结点存入队列中if(root){QueuePush(&Q, root);}//若队列不为空就循环while(!QueueEmpty(&Q)){//接受队头的数据,并删除队头的结点 BTNode* root =QueueFront(&Q);printf("%c ", root->data);QueuePop(&Q);//将队头的左子树、右子树存入队列中(前提是不为空)if(root->left){QueuePush(&Q, root->left);}if(root->right){QueuePush(&Q, root->right);}}//最后不要忘记了销毁队列QueueDestroy(&Q);}// 二叉树销毁voidBinaryTreeDestory(BTNode** root){if(*root ==NULL){return;}BinaryTreeDestory(&(*root)->left);BinaryTreeDestory(&(*root)->right);free(*root);*root =NULL;}// 二叉树节点个数intBinaryTreeSize(BTNode* root){return root ==NULL?0:BinaryTreeSize(root->left)+BinaryTreeSize(root->right)+1;}// 二叉树叶子节点个数intBinaryTreeLeafSize(BTNode* root){if(root ==NULL){return0;}if(root->left ==NULL&& root->right ==NULL){return1;}returnBinaryTreeLeafSize(root->left)+BinaryTreeLeafSize(root->right);}// 二叉树高度intBinaryTreeHight(BTNode* root){//当根节点为空时,返回 0if(root ==NULL){return0;}returnfmax(BinaryTreeHight(root->left),BinaryTreeHight(root->right))+1;//而当根节点为非空时//返回左右子树高度更高的一边并 + 1(根节点本身)}// 二叉树第k层节点个数intBinaryTreeLevelKSize(BTNode* root,int k){//当根节点为空时,返回 0if(root ==NULL){return0;}//当根节点为非空且此时 k == 1 时,返回 1(已经到达第 k 层)if(k ==1){return1;}returnBinaryTreeLevelKSize(root->left, k -1)+BinaryTreeLevelKSize(root->right, k -1);//若该层不是目标层次,就 k - 1 并递归左右子树 }// 二叉树查找值为x的节点 BTNode*BinaryTreeFind(BTNode* root, BTDataType x){if(root ==NULL)//没找到{returnNULL;}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;}}// 判断二叉树是否是完全二叉树intBinaryTreeComplete(BTNode* root){//先创建一个队列来存储结点 Queue Q;QueueInit(&Q);//先将第一个根结点存入队列中if(root){QueuePush(&Q, root);}//若队列不为空就循环while(!QueueEmpty(&Q)){//接受队头的数据,并删除队头的结点 BTNode* root =QueueFront(&Q);QueuePop(&Q);//判断是否遇到空结点,遇到就跳出循环if(root ==NULL){break;}//将队头的左子树、右子树存入队列中QueuePush(&Q, root->left);QueuePush(&Q, root->right);}//继续循环判断是否有残余结点while(!QueueEmpty(&Q)){//接受队头的数据,并删除队头的结点 BTNode* root =QueueFront(&Q);QueuePop(&Q);//判断是否遇到非空结点,遇到则说明,结点不连续,不是完全二叉树if(root !=NULL){//最后不要忘记了销毁队列QueueDestroy(&Q);//返回 0(假)return0;}}//最后不要忘记了销毁队列QueueDestroy(&Q);//最后返回 1(真)return1;}

5. Test.c

用于测试实现的二叉树的运行效果
(这里是小编在写代码时写的测试用例)
(大家在写的时候也要多多测试哦)

#include"BTNode.h"intmain(){char ch[100]="ABD##E#H##CF##G##";int i =0;// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 BTNode* T =BinaryTreeCreate(ch,&i);// 二叉树节点个数int ret1 =BinaryTreeSize(T);printf("二叉树节点个数:%d\n", ret1);// 二叉树叶子节点个数int ret2 =BinaryTreeLeafSize(T);printf("二叉树叶子节点个数:%d\n", ret2);// 二叉树高度int ret3 =BinaryTreeHight(T);printf("二叉树高度:%d\n", ret3);// 二叉树第k层节点个数int ret4 =BinaryTreeLevelKSize(T,3);printf("二叉树第k层节点个数:%d\n", ret4);// 二叉树查找值为x的节点 BTNode* ret5 =BinaryTreeFind(T,'G');if(ret5 ==NULL){printf("没有找到\n");}else{printf("存在%c\n", ret5->data);}// 判断二叉树是否是完全二叉树int ret6 =BinaryTreeComplete(T);if(ret6){printf("是完全二叉树\n");}else{printf("不是完全二叉树\n");}printf("\n");// 二叉树前序遍历 printf("前序:");BinaryTreePrevOrder(T);printf("\n\n");// 二叉树中序遍历printf("中序:");BinaryTreeInOrder(T);printf("\n\n");// 二叉树后序遍历printf("后序:");BinaryTreePostOrder(T);printf("\n\n");// 层序遍历printf("层序:");BinaryTreeLevelOrder(T);printf("\n\n");// 二叉树销毁BinaryTreeDestory(&T);return0;}

结语

本期资料来自于:

在这里插入图片描述

https://legacy.cplusplus.com/

OK,本期的二叉树详解到这里就结束了

若内容对大家有所帮助,可以收藏慢慢看,感谢大家支持

本文有若有不足之处,希望各位兄弟们能给出宝贵的意见。谢谢大家!!!

新人,本期制作不易希望各位兄弟们能动动小手,三连走一走!!!

支持一下(三连必回QwQ)

Read more

【前端】Vue 组件开发中的枚举值验证:从一个Type属性错误说起

【前端】Vue 组件开发中的枚举值验证:从一个Type属性错误说起

🌹欢迎来到《小5讲堂》🌹 🌹这是《小程序》系列文章,每篇文章将以博主理解的角度展开讲解。🌹 🌹温馨提示:博主能力有限,理解水平有限,若有不对之处望指正!🌹 👨💻 作者简介 🏆 荣誉头衔:2024博客之星Top14 | ZEEKLOG博客专家 | 阿里云专家博主 🎤 经历:曾多次进行线下演讲,亦是 ZEEKLOG内容合伙人 以及 新星优秀导师 💡 信念:“帮助别人,成长自己!” 🚀 技术领域:深耕全栈,精通 .NET Core (C#)、Python、Java,熟悉主流数据库 🤝 欢迎交流:无论是基础概念还是进阶实战,都欢迎与我探讨! 目录 * 前言 * 解决过程 * 一、错误场景还原 * 1.1 错误发生的位置 * 1.2 常见的触发场景 * 二、深入理解 Vue

By Ne0inhk

Flutter 三方库 jwt_io 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、严谨、全能的 JSON Web Token (JWT) 加解密与身份安全验证引擎

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 jwt_io 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、严谨、全能的 JSON Web Token (JWT) 加解密与身份安全验证引擎 在鸿蒙(OpenHarmony)系统的端云一体化登录、政企应用的安全审计或复杂的跨端权限校验场景中,如何确保来自云端授信中心的 JWT Token 既能被正确解析(Decode),又能被严密地校验其合法性与过期时间?jwt_io 为开发者提供了一套工业级的、基于 RFC 7519 标准的 JSON Web Token 深度处理方案。本文将深入实战其在鸿蒙应用安全底座中的应用。 前言 什么是 JWT IO?它不仅是一个简单的 Base64 解码器,而是一个具备深厚 RFC

By Ne0inhk
解决 Android WebView 无法加载 H5 页面常见问题的实用指南

解决 Android WebView 无法加载 H5 页面常见问题的实用指南

目录 1. WebView 简介 2. 常见问题 3. 网络权限设置 4. 启用 JavaScript 5. DOM Storage 的重要性 6. 处理 HTTPS 问题 7. 设置 WebViewClient 8. 调试工具 9. 其他调试技巧 10. 结论 相关推荐 1. WebView 简介         Android WebView 是一种视图组件,使得 Android 应用能够显示网页内容。它基于 Chromium,具备现代浏览器的许多功能,包括支持 HTML5、CSS3 和 JavaScript。这使得 WebView 成为展示在线内容和混合应用开发的理想选择。 2.

By Ne0inhk
【递归,搜索与回溯算法 & 记忆化搜索】深入理解记忆化搜索算法:记忆化搜索算法小专题

【递归,搜索与回溯算法 & 记忆化搜索】深入理解记忆化搜索算法:记忆化搜索算法小专题

前言:实现记忆化搜索的一般步骤      (1) 实现记忆化搜索代码步骤         (2) 如何将暴搜代码转换成记忆化搜索代码?         (3)如何添加一个备忘录?         斐波那契数     题目解析         算法原理         解法一:递归        时间复杂度高是因为递归展开树有很多次重复计算,我们可以优化这些重复的计算;我们可以创建一个备忘录,当计算其中一个分支时,把计算出的 d(i) 放入一个"备忘录"中 ( i = 1 ....... n ),当递归其他分支时,我们通过备忘录存储好的计算结果,减少递归树额外重复的展开;     解法二:记忆化搜索    当我们在递归的时候,发现递归过程会重复进行完全相同的问题,我们就把这些完全相同的问题存储到额外创建的"备忘录"中,再后续递归出现相同问题,直接从备忘录中拿计算好的结果即可,避免不必要的重复递归;  所以记忆化搜索,就是一个带备忘录的递归;记忆化搜索,其实也是剪枝的一种方式,在本题使用记忆化搜索,就能把指数级别的时间复杂度降到常数

By Ne0inhk