🐇
🔥博客主页: 云曦
📋系列专栏:数据结构💨吾生也有涯,而知也无涯 💛 感谢大家👍点赞 😋关注📝评论
文章目录
- 前言
- 一、树的概念及结构
- 🌳1.1 树的概念
- 🌳1.4 树和非树
- 🌳1.3 树的表示
- 🌳1.4 树在实际中的运用
- 二、二叉树的概念及结构
- 🌳2.1 二叉树的概念
- 🌳2.2 现实中的二叉树
- 🌳2.3 特殊的二叉树
- 🌳2.4 二叉树的性质
- 🌳2.5 二叉树的存储结构
- ☘️2.5.1 顺序存储
- ☘️2.5.2 链式存储
- 三、二叉树链式结构及实现
- 🌳3.1 二叉树的遍历
- ☘️3.1.1 二叉树前序、中序、后序遍历的规则
- ☘️3.1.2 前序遍历
- ☘️3.1.3 中序遍历
- ☘️3.1.4 后序遍历
- ☘️3.1.5 层序遍历
- 🌳3.2 二叉树节点个数的统计
- ☘️3.2.1 节点的个数
- ☘️3.2.2 叶子节点的个数
- 🌳3.3 二叉树的创建和销毁
- ☘️3.3.2二叉树的创建
- ☘️3.3.2 二叉树的销毁
- 🌳3.3 二叉树接口测试
- 四、完整代码
- 🌳<font color=Red>**4.1 BinaryTree.h**
- 🌳<font color=Red>**4.2 BinaryTree.c**
- 🌳<font color=Red>**4.3 test.c**
前言
在上期,讲解完栈和队列的实现后,本期迎来了新的知识,名为"二叉树",希望大家能坚持的学习下去。
一、树的概念及结构
🌳1.1 树的概念
树跟之前所学的顺序表、链表等不相同,树是一种非线性 的数据结构,它由n(n>=0)个有序节点组成一个具有层次关系的集合。把它叫作树是因为它看起来像一颗倒挂的树,也就是根朝上,而叶子朝下。
- 有一个特殊的节点,称作根节点,根节点没有前驱节点。
- 除根节点外,其余结点被分成M(M>0)个互不相交的集T1T2、……、Tm,其中每一个集合Ti(1<= i<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
- 因此树是由递归定义的
- 树的相关概念:
- 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图: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)棵互不相交的树的集合称为森林;
🌳1.4 树和非树
注意:树形结构中,子树之间是没有交集的,否则就不是树形结构了。
🌳1.3 树的表示
孩子兄弟表示法:
typedef int DataType;
typedef struct TreeNode
{
struct TreeNode* firstchild;//第一个孩子节点
struct TreeNode* nextbrother;//指向下一个兄弟节点
DataType data;//节点的数据域
}TNode;
🌳1.4 树在实际中的运用
用于表示文件系统的目录数结构
二、二叉树的概念及结构
🌳2.1 二叉树的概念
每一颗二叉树是节点的一个有序的集合,该集合的特点是:
- 由一个根和左子树、右子树的组成的二叉树。
- 该树也有可能为空。
从上图可以看出:
- 二叉树不存在大于2的节点。
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。
- 注意:对于任何一颗二叉树都有可能由以下几种复合而成的:
🌳2.2 现实中的二叉树
🌳2.3 特殊的二叉树
- 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
- 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
🌳2.4 二叉树的性质
- 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 个结点.
- 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 .
- 对任何一棵二叉树, 如果度为0其叶结点个数为 , 度为2的分支结点个数为 ,则有 = +1
- 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= . (ps: 是log以2为底,n+1为对数)
- 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
- 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
- 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
- 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子
🌳2.5 二叉树的存储结构
☘️2.5.1 顺序存储
顺序结果存储就是用数组来存储的,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而在使用时只有堆才会用数组来存储,所以本期暂时不讲解顺序存储。
☘️2.5.2 链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们使用的都是二叉链,三叉链会在讲解红黑树时使用。
typedef char BTDataType;
//二叉链
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;//指向当前节点左孩子
struct BinaryTreeNode* right;//指向当前节点右孩子
BTDataType data;//当前节点值域
}BTNode;
//三叉链
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* parents;//指向当前节点的双亲
struct BinaryTreeNode* left;//指向当前节点左孩子
struct BinaryTreeNode* right;//指向当前节点右孩子
BTDataType data;//当前节点值域
}BTNode;
三、二叉树链式结构及实现
结构的创建
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;//左子树
struct BinaryTreeNode* right;//右子树
BTDataType data;//节点的数据域
}BTNode;
🌳3.1 二叉树的遍历
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。
☘️3.1.1 二叉树前序、中序、后序遍历的规则
- 前序遍历:先访问根节点,其次是左子树,最后是右子树(根 -> 左子树 -> 右子树)
- 中序遍历:先访问左子树,其次是根,最后是右子树(左子树 -> 根 -> 右子树)
- 后序遍历:先访问左子树,其次是右子树,最后是根(左子树 -> 右子树 -> 根)
- 举例:
由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为
根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
// 二叉树前序遍历
void PreOrder(BTNode* root);
// 二叉树中序遍历
void InOrder(BTNode* root);
// 二叉树后序遍历
void PostOrder(BTNode* root);
☘️3.1.2 前序遍历
//二叉树前序遍历
void PrevOrder(BTNode* root)
{
//根为空就返回
if (root == NULL)
{
printf("NULL ");
return;
}
//根 -> 左子树 -> 右子树
printf("%c ", root->data);//打印根
PrevOrder(root->left);//递归左子树
PrevOrder(root->right);//递归右子树
}
☘️3.1.3 中序遍历
//二叉树中序遍历
void InOrder(BTNode* root)
{
//根为空就返回
if (root == NULL)
{
printf("NULL ");
return;
}
//左子树 -> 根 -> 右子树
InOrder(root->left);//递归左子树
printf("%c ", root->data);//打印根
InOrder(root->right);//递归右子树
}
☘️3.1.4 后序遍历
void PostOrder(BTNode* root)
{
//根为空就返回
if (root == NULL)
{
printf("NULL ");
return;
}
//左子树 -> 右子树 -> 根
PostOrder(root->left);//递归左子树
PostOrder(root->right);//递归右子树
printf("%c ", root->data);//打印根
}
☘️3.1.5 层序遍历
- 前序中序后序其实也叫:深度优先遍历
- 而层序遍历也叫作:广度优先遍历
- 层序的核心思路就是:上层取出的时候,带下一层进
- 层序遍历需要用到队列来实现,而在C语言中没有队列的库,那么还要实现一个队列。但其实不需要实现,因为上一起已经实现过了,我们直接将源文件和头文件复制粘贴到实现二叉树的目录下即可。(实现栈和队列的链接)
- 实现思路:
注意:引用队列的源文件和头文件后,要在队列的头文件里声明树的结构体,然后将队列结构data的类型改为树的结构体指针类型
且在二叉树的头文件里声明队列的头文件时,要在树的结构体后面声明,因为在编译是头文件是张开向上找结构体的。
void LevelOrder(BTNode* root)
{
//创建及初始化队列
Que 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);
}
}
printf("\n");
QueueDestroy(&q);
}
🌳3.2 二叉树节点个数的统计
☘️3.2.1 节点的个数
int TreeSize(BTNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
看递归很绕的时候教大家一个方法,画递归展开图
☘️3.2.2 叶子节点的个数
int LeafSize(BTNode* root)
{
//根为空就返回0
if (root == NULL)
{
return 0;
}
//左右子树为空,则返回1
if (root->left == NULL && root->right == NULL)
{
return 1;
}
//递归左子树的叶子个数加递归右子树的叶子个数
return LeafSize(root->left) + LeafSize(root->right);
}
相当于节点个数功能的思路延伸,根为空返回0,左子树与右子树都为空返回1,最后递归左右子树相加就得到了叶子节点的个数。
🌳3.3 二叉树的创建和销毁
☘️3.3.2二叉树的创建
- 二叉树的创建思路是:通过前序遍历的数组"ABD##E##C##"构建二叉树
- 只有前序是不可能构建出二叉树的,但是在数组里用’#'表示NULL,这样构建二叉树就很容易了。
BTNode* BinaryTreeCreate(BTDataType* s, int* pi)
{
//遇到'#',加加pi,然后返回NULL
if (s[*pi] == '#')
{
(*pi)++;
return NULL;
}
//malloc出来新的根节点
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
if (root == NULL)
{
perror("malloc fail");
exit(-1);
}
root->data = s[*pi];
(*pi)++;
//递归构建左子树
root->left = BinaryTreeCreate(s, pi);
//递归构建右子树
root->right = BinaryTreeCreate(s, pi);
return root;
}
☘️3.3.2 二叉树的销毁
销毁的含义依然是:把空间还给操作系统。
销毁二叉树很简单,用后序遍历的思路,把打印数据改成free(释放)节点即可。
//思路就是:使用后序遍历思路销毁
void TreeDestroy(BTNode* root)
{
//根为空时,直接返回
if (root == NULL)
{
return;
}
TreeDestroy(root->left);//递归左子树
TreeDestroy(root->right);//递归右子树
free(root);//释放根节点
root = NULL;
}
🌳3.3 二叉树接口测试
int main()
{
//构建树
char str[] = "ABD##E##C##";
int i = 0;
BTNode* root = BinaryTreeCreate(str, &i);
//测试功能
PrevOrder(root);
printf("\n");
InOrder(root);
printf("\n");
PostOrder(root);
printf("\n");
int ATree = TreeSize(root);
printf("%d\n", ATree);//5
int BTree = TreeSize(root->left);
printf("%d\n", BTree);//3
int ALeaf = LeafSize(root);
printf("%d\n", ALeaf);//3
LevelOrder(root);//A B C D E
TreeDestroy(root);
return 0;
}
四、完整代码
🌳4.1 BinaryTree.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
//二叉树
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;//左子树
struct BinaryTreeNode* right;//右子树
BTDataType data;//当前节点值域
}BTNode;
#include "Queue.h"
//二叉树前序遍历
void PrevOrder(BTNode* root);
//二叉树中序遍历
void InOrder(BTNode* root);
//二叉树后序遍历
void PostOrder(BTNode* root);
//二叉树的层次
//核心思路:上层取出的时候,带下一层进
void LevelOrder(BTNode* root);
//二叉树节点的个数
int TreeSize(BTNode* root);
//二叉树叶子的个数
int LeafSize(BTNode* root);
//二叉树的销毁
void TreeDestroy(BTNode* root);
//通过前序遍历的数组"ABD##E##C##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* s, int* pi);
🌳4.2 BinaryTree.c
#include "BinaryTree.h"
void PrevOrder(BTNode* root)
{
//根为空就返回
if (root == NULL)
{
printf("NULL ");
return;
}
//根 -> 左子树 -> 右子树
printf("%c ", root->data);//打印根
PrevOrder(root->left);//递归左子树
PrevOrder(root->right);//递归右子树
}
void InOrder(BTNode* root)
{
//根为空就返回
if (root == NULL)
{
printf("NULL ");
return;
}
//左子树 -> 根 -> 右子树
InOrder(root->left);//递归左子树
printf("%c ", root->data);//打印根
InOrder(root->right);//递归右子树
}
void PostOrder(BTNode* root)
{
//根为空就返回
if (root == NULL)
{
printf("NULL ");
return;
}
//左子树 -> 右子树 -> 根
PostOrder(root->left);//递归左子树
PostOrder(root->right);//递归右子树
printf("%c ", root->data);//打印根
}
int TreeSize(BTNode* root)
{
return root == NULL ? 0 :
TreeSize(root->left) + TreeSize(root->right) + 1;
}
int LeafSize(BTNode* root)
{
//根为空就返回0
if (root == NULL)
{
return 0;
}
//左右子树为空,则返回1
if (root->left == NULL && root->right == NULL)
{
return 1;
}
//递归左子树的叶子个数加递归右子树的叶子个数
return LeafSize(root->left) + LeafSize(root->right);
}
//核心思路:上层取出的时候,带下一层进
void LevelOrder(BTNode* root)
{
//创建及初始化队列
Que 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);
}
}
printf("\n");
QueueDestroy(&q);
}
//思路就是:使用后序遍历思路销毁
void TreeDestroy(BTNode* root)
{
//根为空时,直接返回
if (root == NULL)
{
return;
}
TreeDestroy(root->left);//递归左子树
TreeDestroy(root->right);//递归右子树
free(root);//释放根节点
root = NULL;
}
BTNode* BinaryTreeCreate(BTDataType* s, int* pi)
{
//遇到'#',加加pi,然后返回NULL
if (s[*pi] == '#')
{
(*pi)++;
return NULL;
}
//malloc出来新的根节点
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
if (root == NULL)
{
perror("malloc fail");
exit(-1);
}
root->data = s[*pi];
(*pi)++;
//递归构建左子树
root->left = BinaryTreeCreate(s, pi);
//递归构建右子树
root->right = BinaryTreeCreate(s, pi);
return root;
}
🌳4.3 test.c
#include "BinaryTree.h"
int main()
{
//构建树
char str[] = "ABD##E##C##";
int i = 0;
BTNode* root = BinaryTreeCreate(str, &i);
//测试功能
PrevOrder(root);
printf("\n");
InOrder(root);
printf("\n");
PostOrder(root);
printf("\n");
int ATree = TreeSize(root);
printf("%d\n", ATree);//5
int BTree = TreeSize(root->left);
printf("%d\n", BTree);//3
int ALeaf = LeafSize(root);
printf("%d\n", ALeaf);//3
LevelOrder(root);
TreeDestroy(root);
return 0;
}