【数据结构】二叉树(C语言实现)
文章目录
- 一、树的概念及结构
- 1.树的概念
- 2.树的相关概念名词
- 3.树的表示
- 4.树在实际中的运用
- 二、二叉树概念及结构
- 1.二叉树的概念
- 2.特殊的二叉树
- 3.二叉树的性质
- 4.二叉树的存储结构
- 三、二叉树链式结构的实现
- 1.结构的定义
- 2.构建二叉树
- 3.二叉树前序遍历
- 4.二叉树中序遍历
- 5.二叉树后序遍历
- 6.二叉树层次遍历
- 7.二叉树节点个数
- 8.二叉树叶子节点个数
- 9.二叉树第k层节点个数
- 10.二叉树的高度
- 11.在二叉树中查找值为x的节点
- 12.判断二叉树是否是完全二叉树
- 13.销毁二叉树
- 四、完整代码
- 1.BTree.h
- 2.BTree.c
- 3.test.c
- 4.Queue.h
- 5.Queue.c
一、树的概念及结构
1.树的概念
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
树有一个特殊的结点,称为根结点,根节点没有前驱结点,除根节点外,其余结点被分成(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继节点
因此,树是递归定义的。
【注意】:树形结构中,子树之间不能有交集,否则就不是树形结构
在上面前三个图中,节点直接形成了回路,所以不能称为数,应该称为图。
2.树的相关概念名词
节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G…等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林:由m(m>0)棵互不相交的树的集合称为森林;
3.树的表示
树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法。
typedef int DataType;
struct Node
{struct Node* _firstChild1; // 第一个孩子结点struct Node* _pNextBrother; // 指向其下一个兄弟结点DataType _data; // 结点中的数据域
}
4.树在实际中的运用
树在我们实际生活中的应用之一就是用于表示文件系统的目录:
二、二叉树概念及结构
1.二叉树的概念
一棵二叉树是结点的一个有限集合,该集合:
1.或者为空
2.由一个根节点加上两棵别称为左子树和右子树的二叉树组成
从上图可以看出:
1.二叉树不存在度大于2的结点
2.二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
【注意】对于任意的二叉树都是由以下几种情况复合而成的:
现实中的二叉树:
2.特殊的二叉树
1.满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 2^k-1,则它就是满二叉树。
2.完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
3.二叉树的性质
1.若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 2^(i-1)个结点
2.若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2^h-1
3.对任何一棵二叉树, 如果度为0其叶结点个数为 n0,度为2的分支结点个数为n2 ,则有n0 = n2+1
解析:如图所示:
当二叉树只有一个节点的时候,叶子节点数为1,度为2的分支节点数为0,此时叶子节点数比度为2的节点数多1
当我们增加一个度为1的分支节点的时候,会消耗一个叶子节点,但同时又会产生一个新的叶子节点,所以增加度为1的分支节点时叶子节点的数量不变
当我们增加一个度为2的节点的时候,我们会同时产生一个叶子节点,所以叶子节点数始终比度为2的分支节点多1
4.若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=log(n+1). (是log以2为底,n+1为对数)
高度为h的完全二叉树:2^(h-1) <= N <= 2^h-1
logN+1 <= h <=log(N+1)
5… 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
1.若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
2.若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
3.若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子
概念和性质相关选择题
1.某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )
A 不存在这样的二叉树
B 200
C 198
D 199
【解析】B 根据结论:度为0的节点数比度为2的节点数多1可得n0=200
2.在具有 2n 个结点的完全二叉树中,叶子结点个数为( )
A n
B n+1
C n-1
D n/2
【解析】A 通过完全二叉树的概念我们知道,完全二叉树只存在三种节点,分别为度为0,度为1和度为2的节点,其中度为1的节点要么不存在要么只有一个;又根据度为0的节点数比度为2的节点数多1这个结论,我们可得n0+n1+n0-1=2n,我们知道n0*,n1都为整数,又2n为偶数,我们可知*n1=1;n0=n;
3.一棵完全二叉树的节点数位为531个,那么这棵树的高度为( )
A 11
B 10
C 8
D 12
【解析】B 我们知道 2^(h-1) <= N <= 2^h-1 ,所以h为10
4.二叉树的存储结构
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
1.顺序存储
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
2.链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,红黑树等会用到三叉链。
typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{struct BinTreeNode* _pLeft; // 指向当前节点左孩子struct BinTreeNode* _pRight; // 指向当前节点右孩子BTDataType _data; // 当前节点值域
}
// 三叉链
struct BinaryTreeNode
{struct BinTreeNode* _pParent; // 指向当前节点的双亲struct BinTreeNode* _pLeft; // 指向当前节点左孩子struct BinTreeNode* _pRight; // 指向当前节点右孩子BTDataType _data; // 当前节点值域
}
三、二叉树链式结构的实现
1.结构的定义
// 符号和结构的定义
typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;
2.构建二叉树
由于二叉树不能进行增加和删除操作,所以一般都是给定一个字符串或者一个数组,该字符串或者数组有我们创建二叉树所需要的所有节点,我们根据字符串或者数组的内容来构建二叉树
【注意】字符串或者数组中 # 表示空节点,即上一个节点没有左孩子或者右孩子
// 通过前序遍历的数组 1 2 3 # # 4 5 # # 6 ##构建二叉树
BTNode* BinaryTreeCreat(BTDataType* a, int* pi)
{if (a[*pi] == '#'){(*pi)++;return NULL;}// 创建根节点BTNode* root = (BTNode*)malloc(sizeof(BTNode));if (root == NULL){perror("malloc fail");exit(-1);}root->data = a[*pi];(*pi)++;// 创建左右子树root->left = BinaryTreeCreat(a, pi);root->right = BinaryTreeCreat(a, pi);return root;
}
3.二叉树前序遍历
所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前
中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)
后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后
由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
前序遍历递归图解:
//二叉树先序遍历
void PreOrder(BTNode* root)
{//如果是空树则返回NULLif (root == NULL){printf("NULL ");return;}printf("%d ", root->data); //访问根节点PreOrder(root->left); //先序遍历左子树PreOrder(root->right); //先序遍历右子树
}
4.二叉树中序遍历
//二叉树中序遍历
void InOrder(BTNode* root)
{//如果是空树则返回NULLif (root == NULL){//printf("NULL ");return;}InOrder(root->left); //中序遍历左子树printf("%d ", root->data); //访问根节点//printf("%c ", root->data);InOrder(root->right); //中序遍历右子树
}
5.二叉树后序遍历
/ 二叉树后序遍历
void PostOrder(BTNode* root)
{//如果是空树则返回NULLif (root == NULL){printf("NULL ");return;}PostOrder(root->left); //后序遍历左子树PostOrder(root->right); //后序遍历右子树printf("%d ", root->data); //访问根节点
}
6.二叉树层次遍历
层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历
相比于其他三种遍历方式,层序遍历采用的是非递归的方式,其具体思路是:
利用一个队列来存储二叉树节点的地址,先让父节点入队列,然后父节点出队列,同时父节点的左右孩子会入队列,如果没有就不入,直到队列为空时结束,这样使得当一层节点全部出队列的时候,下一层的节点刚好全部入队列,当队列为空时,二叉树的节点就全部访问完毕了;
【注意】我们用队列来存储二叉树节点的地址,所以我们需要自己实现一个队列,也可以把我们之前实现写的队列Queue.h和Queue.c加入到当前工程中;此外,我们应该将二叉树节点的结构体需要定义在队列结构体的前面。
// 二叉树层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){// 取出队头元素BTNode* front = QueueFront(&q);QueuePop(root);printf("%c ", front->data);// 将队头元素的左右子节点入队列if (front->left)QueuePop(&q, front->left);if (front->right)QueuePop(&q, front->right);}QueueDestroy(&q);
}
7.二叉树节点个数
我们采用子问题思路来解决,我们要计算二叉树节点的个数,那么分为左子树的节点个数和右节点的个数再加上根节点 二叉树节点数 = 左子树节点个数+右节点个数+根节点
/计算二叉树节点个数
int TreeSize(BTNode* root)
{if (root == NULL)return 0;// 左子树节点个数+右节点个数+根节点return TreeSize(root->left) + TreeSize(root->right) + 1;
}
8.二叉树叶子节点个数
和计算二叉树节点个数方法一样,但是叶子节点要求左孩子为空并且右孩子为空,所以叶子节点数等与左右叶子数之和
//计算二叉树叶子节点个数
int TreeLeafSize(BTNode* root)
{//空树返回0if (root == NULL){return 0;}//左子树和右子树均为空则为叶子节点if (root->left == NULL && root->right == NULL){return 1;}//叶子节点数等与左右叶子数之和return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
9.二叉树第k层节点个数
求第k层节点的个数转换成求左右子树的k-1层的节点个数,当k为1 的时候,节点数为1
//第K层节点个数
int TreeKLevel(BTNode* root, int k)
{assert(k > 0); //层数大于0//空树返回0if (root == NULL){return 0;}if (k == 1){return 1;}//相对于根是第k层,则相对于根是子树的k-1层!!!//换成求子树第k-1层return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);}c
10.二叉树的高度
树的高度等于左子树的高度和右子树的高度的最大值+1
//计算二叉树深度
int TreeHeight(BTNode* root)
{//如果是空树返回0if (root == NULL){return 0;}int lret = TreeHeight(root->left); //递归计算左子树的深度记为lretint rret = TreeHeight(root->right); //递归计算右子树的深度记为rret/*if (lret > rret){return lret + 1;}else{return rret + 1;}*///二叉树的深度为lret和rret的较大者+1return lret > rret ? lret + 1 : rret + 1;
}
11.在二叉树中查找值为x的节点
我们先在左子树找没有找到再到右子树找,都没有找到则返回NULL;注意的是,上一个节点的返回值将作为下一个节点是否继续找的依据,所以我们要用一个指针保存左右子树查找的返回值,再进行判断。
// 在二叉树中查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->data == x)return root;// 先去左树找BTNode* lret = TreeFind(root->left, x);if (lret)return lret;// 左树没有找到,再到右树找BTNode* rret = TreeFind(root->right, x);if (rret)return rret;//return TreeFind(root->right, x);// 都找不到则返回空retun NULL;
}
12.判断二叉树是否是完全二叉树
我们知道,高度为h的完全二叉树,前h-1层都是满二叉树,最后一层不一定是满二叉树,但是最后一层的节点必须是连续的,也就是说,当完全二叉树遇到空节点的时候,后面就不会在出现非空的节点,否则就不是完全二叉树
根据上面完全二叉树的性质,我们可以利用二叉树的层序遍历来判断二叉树是否是完全二叉树,基本思路为对二叉树进行层序遍历,不管节点是否为空都入队列,当队头的元素为空的时候,我们检查队列中的剩余数据是否都是空节点,如果含有非空节点则该树就不是完全二叉树。
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);if (front == NULL)break;QueuePop(&q);QueuePush(&q, root->left);QueuePush(&q, root->right);}// 遇到空以后,后面全是空,则是完全二叉树// 遇到空以后,后面存在非空,则不是完全二叉树while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);if (front != NULL){QueueDestroy(&q);return false;}QueuePop(&q);}return true;
}
13.销毁二叉树
我们不能直接删除根节点,需要采用后续遍历的方式进行依次删除
// 销毁二叉树
void BinaryTreeDestroy(BTNode* root)
{if (root == NULL)return;// 通过后续遍历来销毁节点BinaryTreeDestroy(root->left);BinaryTreeDestroy(root->right);// 此处置空不会影响外面,需要在外面进行置空free(root);
}
四、完整代码
1.BTree.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;//创建二叉树
BTNode* CreateTree();
//二叉树先序遍历
void PreOrder(BTNode* root);
//二叉树中序遍历
void InOrder(BTNode* root);
//二叉树后序遍历
void PostOrder(BTNode* root);
// 二叉树层序遍历
void BinaryTreeLevelOrder(BTNode* root);
//计算二叉树节点个数
int TreeSize(BTNode* root);
//计算二叉树深度
int TreeHeight(BTNode* root);
//第K层节点个数
int TreeKLevel(BTNode* root, int k);
//计算二叉树叶子节点个数
int TreeLeafSize(BTNode* root);
//返回x所在的节点
BTNode* TreeFind(BTNode* root, BTDataType x);
//创建二叉树
BTNode* BTreeCreate(char* a, int* pi);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);
// 销毁二叉树
void BinaryTreeDestroy(BTNode* root);
2.BTree.c
#include "BTree.h"//创建二叉树
BTNode* CreateTree()
{//创建节点BTNode* n1 = (BTNode*)malloc(sizeof(BTNode));assert(n1);BTNode* n2 = (BTNode*)malloc(sizeof(BTNode));assert(n2);BTNode* n3 = (BTNode*)malloc(sizeof(BTNode));assert(n3);BTNode* n4 = (BTNode*)malloc(sizeof(BTNode));assert(n4);BTNode* n5 = (BTNode*)malloc(sizeof(BTNode));assert(n5);BTNode* n6 = (BTNode*)malloc(sizeof(BTNode));assert(n6);BTNode* n7 = (BTNode*)malloc(sizeof(BTNode));assert(n7);//链接关系n1->data = 1;n2->data = 2;n3->data = 3;n4->data = 4;n5->data = 5;n6->data = 6;n7->data = 7;n1->left = n2;n1->right = n4;n2->left = n3;n2->right = NULL;n4->left = n5;n4->right = n6;n3->left = NULL;n3->right = NULL;n5->left = NULL;n5->right = NULL;n6->left = NULL;n6->right = NULL;n3->right = n7;n7->left = NULL;n7->right = NULL;return n1;
}//二叉树先序遍历
void PreOrder(BTNode* root)
{//如果是空树则返回NULLif (root == NULL){printf("NULL ");return;}printf("%d ", root->data); //访问根节点PreOrder(root->left); //先序遍历左子树PreOrder(root->right); //先序遍历右子树
}//二叉树中序遍历
void InOrder(BTNode* root)
{//如果是空树则返回NULLif (root == NULL){//printf("NULL ");return;}InOrder(root->left); //中序遍历左子树printf("%d ", root->data); //访问根节点//printf("%c ", root->data);InOrder(root->right); //中序遍历右子树
}// 二叉树后序遍历
void PostOrder(BTNode* root)
{//如果是空树则返回NULLif (root == NULL){printf("NULL ");return;}PostOrder(root->left); //后序遍历左子树PostOrder(root->right); //后序遍历右子树printf("%d ", root->data); //访问根节点
}// 二叉树层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){// 取出队头元素BTNode* front = QueueFront(&q);QueuePop(root);printf("%c ", front->data);// 将队头元素的左右子节点入队列if (front->left)QueuePop(&q, front->left);if (front->right)QueuePop(&q, front->right);}QueueDestroy(&q);
}//int count = 0;//定义全局变量,导致两次调用返回值不一样//计算二叉树节点个数
int TreeSize(BTNode* root)
{//知易行难(不行)遍历计数//static int count = 0;//static修饰count成为全局变量,导致两次返回值不一样!!!// 第一次打印7,则第二次打印14!!!//if (root == NULL)// return count;// //++count;//TreeSize(root->left);//TreeSize(root->right);// // return count;/*if (root == NULL){return 0;}int lret = TreeSize(root->left);int rret = TreeSize(root->right);return TreeSize(root->left) + TreeSize(root->right) + 1;*//*if (root == NULL){return 0;}return TreeSize(root->left) + TreeSize(root->right) + 1;*///二叉树的节点个数等于左子树的个数+右子树的深度+1return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}//计算二叉树深度
int TreeHeight(BTNode* root)
{//如果是空树返回0if (root == NULL){return 0;}int lret = TreeHeight(root->left); //递归计算左子树的深度记为lretint rret = TreeHeight(root->right); //递归计算右子树的深度记为rret/*if (lret > rret){return lret + 1;}else{return rret + 1;}*///二叉树的深度为lret和rret的较大者+1return lret > rret ? lret + 1 : rret + 1;
}//第K层节点个数
int TreeKLevel(BTNode* root, int k)
{assert(k > 0); //层数大于0//空树返回0if (root == NULL){return 0;}if (k == 1){return 1;}//相对于根是第k层,则相对于根是子树的k-1层!!!//换成求子树第k-1层return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);}//计算二叉树叶子节点个数
int TreeLeafSize(BTNode* root)
{//空树返回0if (root == NULL){return 0;}//左子树和右子树均为空则为叶子节点if (root->left == NULL && root->right == NULL){return 1;}//叶子节点数等与左右叶子数之和return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}//返回x所在的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{//空树返回NULLif (root == NULL){return NULL;}//根节点返回root的地址 if (root->data == x){return root;}//先在左子树找BTNode* lret = TreeFind(root->left, x);if (lret){return lret;}//左子树没找到,去右子树找BTNode* rret = TreeFind(root->right, x);if (rret){return rret;}//不推荐,可读性不强,不容易理解//return TreeFind(root->right, x);return NULL;
}BTNode* BTreeCreate(char* a, int* pi)
{//输入字符为‘#’returnif (a[*pi] == '#'){(*pi)++;return NULL;}//创建新节点BTNode* root = (BTNode*)malloc(sizeof(BTNode));//空间未开辟成功,退出程序if (root == NULL){perror("malloc fail");exit(-1);}//数组的字符赋给根节点root->data = a[*pi];(*pi)++;root->left = BTreeCreate(a, pi); //递归创建左子树root->right = BTreeCreate(a, pi); //递归创建右子树return root;
}
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);if (front == NULL)break;QueuePop(&q);QueuePush(&q, root->left);QueuePush(&q, root->right);}// 遇到空以后,后面全是空,则是完全二叉树// 遇到空以后,后面存在非空,则不是完全二叉树while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);if (front != NULL){QueueDestroy(&q);return false;}QueuePop(&q);}return true;
}// 销毁二叉树
void BinaryTreeDestroy(BTNode* root)
{if (root == NULL)return;// 通过后续遍历来销毁节点BinaryTreeDestroy(root->left);BinaryTreeDestroy(root->right);// 此处置空不会影响外面,需要在外面进行置空free(root);
}
3.test.c
#include "BTree.h"int main()
{//创建二叉树BTNode* root = CreateTree();//先序遍历二叉树PreOrder(root);printf("\n");//计算二叉树节点个数printf("Tree size:%d\n", TreeSize(root));printf("Tree size:%d\n", TreeSize(root));//计算二叉树高度printf("Tree Height:%d\n", TreeHeight(root));//计算第k层节点个数printf("Tree KLevel:%d\n", TreeKLevel(root, 1));printf("Tree KLevel:%d\n", TreeKLevel(root, 2));printf("Tree KLevel:%d\n", TreeKLevel(root, 3));printf("Tree KLevel:%d\n", TreeKLevel(root, 4));//查找x所在的节点BTNode* ret = TreeFind(root, 7);printf("ret=%p\n", ret);printf("retbefore:%d\n", ret->data);//修改x所在的节点的值ret->data *= 10;printf("retafter:%d\n", ret->data);//计算二叉树叶子节点个数printf("Tree LeafSize:%d\n", TreeLeafSize(root));//测试创建二叉树//char str[100]; //创建数组//scanf("%s", str); //输入字符//int i = 0; //记录数组的下标递归i的值不会改变,所以传i的地址!!!//BTNode* root = BTreeCreate(str, &i);//InOrder(root);return 0;
}
4.Queue.h
#pragma once //防止头文件被重复包含//包含头文件
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef char BTDataType;
typedef struct BinaryTree
{BTDataType data;struct BinaryTree* left;struct BinaryTree* right;
}BTNode;//结构和符号的定义
typedef int QDataType; //数据类型重定义//定义队列的一个节点
typedef struct QueueNode
{struct QueueNode* next;QDataType data;
}QNode;typedef struct Queue
{QNode* head; //记录队列的头QNode* tail; //记录队列的尾int size; //记录队列的长度
}Queue;//函数的声明
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestroy(Queue* pq);
//队尾入队列
void QueuePush(Queue* pq, QDataType x);
//对头出队列
void QueuePop(Queue* pq);
//获取对头元素
QDataType QueueFront(Queue* pq);
//获取队尾元素
QDataType QueueBack(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
//返回队列元素个数
int QueueSize(Queue* pq);
5.Queue.c
#include "Queue.h"//初始化队列
void QueueInit(Queue* pq)
{assert(pq);pq->head = pq->tail = NULL;pq->size = 0;
}//销毁队列
void QueueDestroy(Queue* pq)
{assert(pq);//遍历删除QNode* cur = pq->head;while (cur){QNode* del = cur;cur = cur->next;free(del);}pq->head = pq->tail = NULL;
}//队尾入队列
void QueuePush(Queue* pq, QDataType x)
{assert(pq);//开辟新节点QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");exit(-1);}else{//节点的数据复制为x,指针置为空newnode->data = x;newnode->next = NULL;}//空队列在队列头部if (pq->head == NULL){pq->head = pq->tail = newnode;}else{//尾指针后移pq->tail->next = newnode;pq->tail = newnode;}pq->size++;
}//对头出队列
void QueuePop(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq)); //队列为空时不能出队列//只有一个元素的时候,出队列之后,头尾指针都置为空if (pq->head->next == NULL){free(pq->head);pq->head = pq->tail = NULL;}else{QNode* del = pq->head;pq->head = pq->head->next;free(del);del = NULL;}pq->size--;
}//获取对头元素
QDataType QueueFront(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->head->data;
}//获取队尾元素
QDataType QueueBack(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->tail->data;
}//判断队列是否为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->head == NULL && pq->tail == NULL;
}//返回队列元素个数
int QueueSize(Queue* pq)
{assert(pq);/*int count = 0;QNode* cur = pq->head;while (cur){cur = cur->next;count++;}return count;*/return pq->size;
}
相关文章:

【数据结构】二叉树(C语言实现)
文章目录一、树的概念及结构1.树的概念2.树的相关概念名词3.树的表示4.树在实际中的运用二、二叉树概念及结构1.二叉树的概念2.特殊的二叉树3.二叉树的性质4.二叉树的存储结构三、二叉树链式结构的实现1.结构的定义2.构建二叉树3.二叉树前序遍历4.二叉树中序遍历5.二叉树后序遍…...

高级信息系统项目管理(高项 软考)原创论文——成本管理(2)
1、如果您想了解如何高分通过高级信息系统项目管理师(高项)你可以点击链接: 高级信息系统项目管理师(高项)高分通过经验分享_高项经验 2、如果您想了解更多的高级信息系统项目管理(高项 软考)原创论文,您可以点击链接:...

代码签名即将迎来一波新关注
在数字化高度发展的当下,个人隐私及信息安全保护已经成了大家关注的重点,包括日常使用的电脑软件,手机APP等,由于包含了大量的用户信息,已经成了重点关注对象,任何一个疏忽就可能泄露大量用户信息。所以权威…...

黑盒渗透盲打lampiao
一、查找主机ip,通过Nmap扫描工具排查出我的靶机的IP 为.134 python tools.py ip -i 192.168.12.0 -h 254 -l 1 二、扫描其他端口。 1898 三、查看网站漏洞情况,典型的漏洞特征 Ac扫描漏洞情况,利用典型的漏洞。 四、开始getshell 1、启动M…...

笔记:VLAN及交换机处理详细教程(Tagged, UnTagged and Native VLANS Tutorial)
一、内容来源 本文是对下面这篇文章的总结,写的很全、很细致、干货满满,强力推荐: 《Tagged, UnTagged and Native VLANS Tutorial – A Quick Guide about What they Are?》 二、为什么引入VLAN? 早期设备间通过集线器&#x…...

在字节跳动,造赛博古籍
“你在字节跳动哪个业务?”“古籍数字化。把《论语》《左传》《道德经》这些古籍变成电子版,让大家都能免费看。”没错,除了你熟悉的那些 App,字节跳动还在做一些小众而特别的事情,古籍数字化就是其中之一。在字节跳动…...

Android 12.0设置默认Launcher安装一款Launcher默认Launcher无效的解决方案
1.概述 在12.0的系统rom定制化过程中,在系统中当有多个Launcher的时候,这时候会要求设置默认Launcher,但是在最近的产品开发过程中,发现在设置完默认Launcher以后,在安装个Launcher的时候,会让原来设置的默认Launcher变为空了,就是在系统Settings中的默认应用中,launche…...

数据结构第16周 :( 希尔排序+ 堆排序 + 快速排序 )
目录希尔排序堆排序快速排序希尔排序 【问题描述】给出一组数据,请用希尔排序将其按照从小到大的顺序排列好。 【输入形式】原始数据,以0作为输入的结束;第二行是增量的值,都只有3个。 【输出形式】每一趟增量排序后的结果 【…...

【C++】类和对象
1.面向过程和面向对象初步认识 我们知道,C语言是面向过程的,关注的就是问题解决的过程; C是面向过程和面向对象混编,因为C兼容了C语言,而面向对象关注的不再是问题解决的过程; 而是一件事情所关联的不同…...

Java缓存面试题——Redis应用
文章目录1、为什么要使用Redis做缓存?2、为什么Redis单线程模型效率也能那么高?3、Redis6.0为什么要引入多线程呢?4、Redis常见数据结构以及使用场景字符串(String)哈希(Hash)列表(list)集合&am…...

KMP算法详细理解
一、目的1.KMP应用场景:可以解决字符串匹配问题; 在一个串中查找是否出现过另一个串。2.KMP的经典思想就是:当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。3.KMP算法关键在于&…...

RabbitMQ单节点安装
在学习RabbitMQ之前,必须要把RabbitMQ的环境搭建起来,刚开始学习时,搭建单节点是入门RabbitMQ最方便、最快捷的方式,这篇文章就是介绍如何使用RabbitMQ压缩包的方式搭建一个单节点的RabbitMQ。 在实际项目中,服务器都…...

tomcat 服务的目录结构和tomcat的运行模式
目录 一、tomcat 服务的目录结构解析: 1、tomcat目录结构: bin目录: conf目录: lib目录: logs目录: temp目录: webapps目录: wokr目录: 二、tomcat服务的运行模…...

vector迭代器失效问题
一、迭代器: 迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所…...

2023年排名前茅的十大饭店装修设计!
相信大家都是知道的,饭店装修设计其实是一门很深的学问,只有掌握这门学问才能够打造出来精美的空间,因此饭店装修必须要有专业餐饮设计公司的设计师进行设计。但是在国内饭店装修设计公司那么多,饭店老板要如何选择呢?…...

MFCCA多通道多说话人语音识别模型上线魔搭(ModelScope)
实验室研发的基于多帧跨通道注意力机制(MFCCA)的多说话人语音识别模型近日上线魔搭(ModelScope)社区,该模型在AliMeeting会议数据集上获得当前最优性能。欢迎大家下载。开发者可以基于此模型进一步利用ModelScope的微调…...

刷题记录:牛客NC25078[USACO 2007 Ope S]City Horizon
传送门:牛客 题目描述: Farmer John has taken his cows on a trip to the city! As the sun sets, the cows gaze at the city horizon and observe the beautiful silhouettes formed by the rectangular buildings. The entire horizon is represented by a number line …...

【Java|golang】 1238. 循环码排列---格雷编码
给你两个整数 n 和 start。你的任务是返回任意 (0,1,2,…,2^n-1) 的排列 p,并且满足: p[0] start p[i] 和 p[i1] 的二进制表示形式只有一位不同 p[0] 和 p[2^n -1] 的二进制表示形式也只有一位不同 示例 1: 输入:n 2, start …...

Python自动化测试框架封装和调用
封装与调用函数与参数化前言 面实现了参数的关联,那种只是记流水账的完成功能,不便于维护,也没什么可读性,接下来这篇可以把每一个动作写成一个函数,这样更方便了。参数化的思维只需记住一点:不要写死 登录…...

线程的执行
承接上文CPU原理简介程序的执行是由控制器发信号推动整个程序一步一步向前走,将数据存储在寄存器,从程序计数器中获取指令,比如先把3放到寄存器,再把5放到寄存器,再做一个加法,加法就是一个指令,…...

【视频】海康摄像头、NVR网络协议简介
1、软硬件整体架构 2、涉及的网络协议 3、协议简介 3.1 海康私有协议 设备发现SADP:进行设备的发现、激活、修改网络参数、忘记密码等; SDK:4200、系统平台的接入前端设备,协议不对外开放,但对外提供接口库; ISAPI:Intelligent Security API(智能安全API),基于HTTP传输…...

【Spring的事务传播行为有哪些呢?Spring事务的隔离级别?讲下嵌套事务?】
如果你想寻求一份与后端相关的开发工作,那么关于Spring事务相关的面试题你就不能说不会并且不能不知道? 人生如棋,我愿为卒,行动虽慢,可谁曾见我后退一步? 一.Spring中声明事务的方式 1.1 编程式事务 编程…...

其实一点不难学会这三步一定让你学会制作一个『3D建模』大屏
上次已经教过大家怎样制作一个简单的2D数据可视化大屏~那有一些朋友们就会说那些炫酷的3D可视化大屏是怎样制作的呢?这不就来了,今天就教大家怎样用山海鲸可视化软件制作一个带3D建模的可视化大屏,并且最重要的是无需会特别复杂的3D建模知识。…...

【C++】C++的内存模型之四大分区
程序的内存模型 C程序在执行时,将内存大方向划分为4个区域 代码区:存放函数体的二进制代码,由操作系统进行管理的全局区:存放全局变量和静态变量以及常量栈区:由编译器自动分配释放,存放函数的参数值&…...

Vue跨级通信(重点)
当不使用Vuex的前提下,子孙传递就得使用另外一种办法:provide 和 inject 总结:provide / inject 类似于消息的订阅和发布。- inject接收数据。- provide提供或发送数据,(1)provide(name…...

支付系统中的设计模式07:责任链模式
最近公司业务的发展果然如老板当初所画(预)饼(言)的那样红(恍)红(恍)火(惚)火(惚),蒸蒸日上,每天的流水都在不断攀升到新的高度,有不少人都从公司开发的电商平台挣到了钱。 不过问题也接着来了——运营部门经过老板的同意,也学着产品经理提出了下面几项非常合理…...

期末综合考试
一、概率论1、全概率公式、贝叶斯公式应用2、期望、方差、协方差的定义以及性质证明(1) 期望(2) 方差(3) 协方差二、数理统计1、参数估计(1) 矩估计(2) 最大似然估计(3) 综合例题一、概率论 1、全概率公式、贝叶斯公式应用 记住标黄的两段,上考场直接套数据&#x…...

数据结构与算法之爬楼梯动态规划
一.题目(爬楼梯)假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数。示例 1:输入: 2输出: 2解释: 有两种方法可以爬…...

CleanMyMac4.12最新Mac电脑系统垃圾清理神器
CleanMyMac是Mac一款神器,特别是清理已卸载软件残留垃圾文件信息库比较全面。 clearmymac以极其快速和时尚的方式为您提供及时的建议,组织,更新和保护您的Mac。完全支持macOS 11(Big Sur)操作系统;它以其简…...

数据治理如何做?火山引擎 DataLeap 帮助这款产品 3 个月降低计算成本 20%
更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群 本文讲述字节跳动一款 App 产品的数据治理故事。该产品随着用户体量和数据体量不断增长,数仓的任务量、数据量也不断攀升,运维难、成本贵、稳…...