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

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

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

前言

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

一、树是什么?

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

Python 流程控制完全指南:条件语句 + 循环语句 + 实战案例(零基础入门)

Python 流程控制完全指南:条件语句 + 循环语句 + 实战案例(零基础入门)

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 顺序语句:基础执行语句 * 二. 条件语句:实现 “如果… 否则…” 逻辑 * 2.1 核心语法格式 * 2.2 关键注意点 * 2.3 空语句 pass:占位符作用 * 2.4 练习题 * 三. 循环语句:实现 “重复执行” 逻辑 * 3.1 while 循环:条件满足就一直执行 * 3.2 for 循环:

By Ne0inhk
在线浏览“秀人网合集”的新思路:30 行 Python 把封面图链接秒变本地可点图库

在线浏览“秀人网合集”的新思路:30 行 Python 把封面图链接秒变本地可点图库

用 30 行 Python 把秀人网公开合集“搬”进本地数据库 “秀人网”近日上线的新主题合集页采用前端渲染,数据通过 /api/v2/theme/list 接口一次性返回 JSON,无需模拟点击“加载更多”。接口无登录限制,但带 5 秒滑动窗口的 IP 频次校验:单 IP >30 次/分即返回 429。本文示范如何遵守 robots 协议、放缓速率,仅采集“公开可见”字段,并给出断点续抓、User-Agent 随机化、异常重试等常用技巧。 核心思路三步走: 分析列表接口:在浏览器 DevTools 里筛选 XHR,发现真实请求 URL

By Ne0inhk
Python 字符串操作

Python 字符串操作

Python 字符串操作 概述 字符串是编程中最常用的数据类型之一,特别是在文本处理、数据清洗和自然语言处理等领域。掌握字符串操作是Python编程的基础技能。 字符串操作分类 字符串操作 基本操作 常用方法 格式化 编码处理 类型转换 高级操作 索引 切片 长度 大小写转换 查找替换 分割连接 判断方法 百分号格式化 format方法 f-string 编码 解码 数字转字符串 字符串转数字 对齐填充 迭代遍历 代码示例 #!/usr/bin/env python3# -*- coding: utf-8 -*-""" 文件名: string_operations.py 开发思路和开发过程:

By Ne0inhk
【Python 量化入门】AKshare 保姆级使用教程:零成本获取股票 / 基金 / 期货全市场金融数据

【Python 量化入门】AKshare 保姆级使用教程:零成本获取股票 / 基金 / 期货全市场金融数据

做量化交易、财经数据分析、投资复盘的开发者和投资者,经常会遇到核心痛点:付费金融数据接口成本高、免费 API 注册流程繁琐、多市场数据分散难以整合。告别 QMT 回测烦恼!手把手教你搭建 MiniQMT+Backtrader 量化回测框架 本文就给大家详细讲解 Python 量化圈的开源神器AKshare,从安装到核心功能实战全覆盖,代码可直接复制运行,零基础也能一键获取全市场金融行情数据。 一、AKshare 是什么? AKshare 是一款基于 Python 开发的开源金融数据接口库,专为个人投资者、量化爱好者、财经数据分析人员打造,是目前国内生态最完善、维护最活跃的免费金融数据工具之一。 它支持股票、期货、基金、外汇、债券、指数、加密货币等多种主流金融市场的数据获取,核心优势如下: * 免费开源:完全开源免费,无隐藏收费,个人非商用零成本使用,无需开通付费会员 * 数据覆盖全面:A 股、

By Ne0inhk