文章目录
一.树的基本概念
树(Tree)是一种非线性的数据结构,它模拟了具有层次关系的数据的集合。树由多个节点(Node)组成,这些节点通过边(Edge)相互连接,但不存在任何从一个节点到另一个节点的环(仅是逻辑结构上存在)。
1.树的定义
-
树是由n(n≥0)个节点组成的有限集合。当n=0时,称为空树;当n>0时,存在以下特点:
- 有一个特定的节点称为根节点(Root),它是唯一的,没有父节点。
- 当n>1时,其余节点可分为m(m>0)个互不相交的有限集T1、T2…Tm,其中每一个集合Ti(1<=i<=m)本身也是一棵树,并称为根的子树(Subtree)。
-
树的节点:
- 每个节点都包含一个数据元素以及若干指向其子树的指针(或称为分支)。
- 没有父节点的节点是根节点。
- 每一个非根节点有且只有一个父节点。
- 除了根节点外,每个节点都可以被看作是其子树的根节点。
-
树的度:
- 节点的度(Degree)是指该节点拥有的子树(或称为子节点)的数量。
- 树的度是指树内所有节点的度的最大值。
-
树的种类:
- 无序树(也称为自由树):树中任意节点的子节点之间没有顺序关系。
- 有序树:树中任意节点的子节点之间有顺序关系。
- 二叉树:每个节点最多有两个子节点的树。
- 完全二叉树:除最后一层外,每一层都被完全填满,且所有节点都尽可能向左对齐的二叉树。
- 满二叉树:除了叶子节点外,每个节点都有两个子节点的二叉树。
-
树的其他术语:
- 叶子节点(Leaf)/终端节点:度为0的节点。
- 分支节点/非终端节点:度不为0的节点。
- 内部节点:除根节点以外,分支节点也称为内部节点。
- 孩子(Child)和双亲(Parent):节点的子树的根称为该节点的孩子,相应的,该节点称为孩子的双亲。
- 兄弟(Sibling):同一个双亲的孩子之间互称兄弟。
-
树的表示方法:
- 常用的表示方法包括图像表示法、双亲表示法、孩子表示法、孩子兄弟表示法等。
树结构在客观世界中广泛存在,如人类社会的族谱、组织结构等,同时也在计算机领域中得到广泛应用,如文件系统的表示、数据库的索引等
2.树的性质
1.结点数与度数关系:树中的结点数等于所有节点的度数之和加1。这里,节点的度数是指该节点拥有的子节点的数量。
2.层数与节点数:
- 在度为m的树中,第i层上最多有m^(i-1)个节点(i≥1)。
- 高度为h的m次树(或m叉树)最多有(m^h - 1) / (m - 1)个节点。
3.最小高度:
-
具有n个节点的m叉树的最小高度可以通过公式计算得出,一般形式为⌈log_m(n(m-1)+1)⌉。
(需要注意的是,这里的计算方式假设树是“满”的或“完全”的,即尽可能在每个层级上填充节点。)
二.二叉树的概念
二叉树(Binary Tree)是树形结构的一个重要类型,它是一种每个节点最多有两个子节点的有序树。二叉树的定义和特性如下:
1.二叉树的定义
二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成。二叉树是递归定义的,即二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树。
2.二叉树的特性
-
节点结构:每个节点最多只能有两个子节点,称为左子节点和右子节点。
-
有序性:左子树和右子树是有序的,即不能互换。
-
递归性:二叉树是递归定义的,即一个二叉树要么为空,要么包含一个根节点和两个子二叉树。
-
层次性:二叉树的节点按照层次结构组织,每个节点都可能有子节点,但每个节点只有一个父节点(除了根节点)。
-
节点计数:在二叉树中,第k层上至多有2(k-1)个节点(k>0)**,**高度为h的二叉树至多有2h-1个节点(h>0)。
-
叶子节点和度为2的节点关系:对于任意一棵二叉树,如果其叶节点数为N0,而度数为2的节点总数为N2,则N0=N2+1。
-
完全二叉树:若设二叉树的高度为h,除第h层外,其它各层(1~h-1)的节点数都达到最大个数,第h层有叶子节点,并且叶子节点都是从左到右依次排布,这就是完全二叉树。
-
满二叉树:除了叶节点外每一个节点都有左右子节点且叶节点都处在最底层的二叉树。
三.二叉树的实现
1.二叉树的存储结构
二叉树的存储结构分为顺序存储和链式存储。
顺序存储是指用一组连续的存储单元依次自上而下,自左至右存储完全二叉树上的节点元素,即将完全二叉树上编号为i的节点元素存储在一维数组下标为i-1的分量中。
由于顺序存储的空间利用率较低,因此二叉树一般都采用链式存储结构,用链式节点来存储二叉树中的每个节点。在二叉树中,节点结构通常包括若干数据域和若干指针域,二叉链表至少包含3个域,数据域data,左指针域left,右指针域right,如图所示
二叉树的链式存储结构描述如下:
typedef int BTDataType;
typedef struct BinaryTreeNode {
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
现阶段我们重点学习二叉树的结构(如二叉树的遍历,查找节点个数等…),对于二叉树的增删查改我们将会在之后的学习中了解到。
我们先手动构建一个二叉树,再来了解树的结构,这里是文件Tree.c
主要内容,(测试结果我将放在每个接口函数代码之后)。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include"queue.h"
typedef int BTDataType;
typedef struct BinaryTreeNode {
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
//创建树的节点
BTNode* CreateBTNode(BTDataType x) {
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL) {
perror("malloc false");
return NULL;
}
newnode->data = x;
newnode->left = NULL;
newnode->right = NULL;
return newnode;
}
//创建树
BTNode* CreateTree() {
BTNode* node1 = CreateBTNode(1);
BTNode* node2 = CreateBTNode(2);
BTNode* node3 = CreateBTNode(3);
BTNode* node4 = CreateBTNode(4);
BTNode* node5 = CreateBTNode(5);
BTNode* node6 = CreateBTNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
//主函数,用来测试
int main() {
//创建树
BTNode* root = CreateTree();
printf("前序遍历:\n");
PerOrder(root);
printf("\n");
printf("中序遍历:\n");
InOrder(root);
printf("\n");
printf("后序遍历\n");
PostOrder(root);
printf("\n");
printf("层序遍历:\n");
LevelOrder(root);
printf("\n");
int size = TreeSize(root);
printf("树的节点个数:\n");
printf("TreeSize=%d\n", size);
int height = TreeHeight(root);
printf("树的高度:\n");
printf("TreeHeight=%d\n", height);
int kLevel = TreeKLevel(root, 3);
printf("获取树的第三层节点个数:\n");
printf("TreeLevel=%d\n", kLevel);
BTNode* ret = TreeNodeFind(root, 4);
printf("输出查找节点的值:\n");
printf("%d \n", ret->data);
printf("当前二叉树是否是完全二叉树,是输出1,不是输出0\n");
printf("%d", LevelTree(root));
return 0;
}
2.二叉树的遍历
2.1.PerOrder
前序遍历
若二叉树为空,则什么也不做;否则:
1.访问根节点;
2.先序遍历左子树;
3.先序遍历右子树。
void PerOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
printf("%d ", root->data);
PerOrder(root->left);
PerOrder(root->right);
}
2.2.InOrder
中序遍历
若二叉树为空,则什么也不做;否则:
1.中序遍历左子树;
2.访问根节点;
3.中序遍历右子树。
void InOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
2.3.PostOrder
后序遍历
若二叉树为空,则什么也不做;否则:
1.后序遍历左子树;
2.后序遍历右子树;
3.访问根节点。
void PostOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
2.4.LevelOrder
层序遍历
层序遍历时需要借助队列来实现,队列的数据域存放树的结点指针,(队列在我之前的文章中有讲解到,可以看我之前的文章,这里队列的代码就不再写了),下面通过一个例子来演试如何通过一个队列实现树的层序遍历。
void LevelOrder(BTNode* root) {
Queue q;
QueueInit(&q);
if (root) {
QueuePush(&q, root);
}
while (!QueueIsEmpty(&q)) {
BTNode* front = GetQueueFront(&q);
QueuePop(&q);
printf("%d ", front->data);
if (front->left) {
QueuePush(&q, front->left);
}
if (front->right) {
QueuePush(&q, front->right);
}
}
2.5.测试结果
ps:一般情况下,前中后序遍历时不需要打印NULL,这里把NULL打印上是便于理解,自己尝试测试时可以删去。
3.二叉树的接口函数
3.1.获取树的节点个数
左数节点个数+右数节点个数+1,遇到空节点返回0。
int TreeSize(BTNode* root) {
if (root == NULL) {
return 0;
}
return TreeSize(root->left) + TreeSize(root->right) + 1;
}
3.2.获取树的层数
先分别获取左子树和右子树的高度,哪个大就返回再加一,遇到空节点返回0。
int TreeHeight(BTNode* root) {
if (root == NULL) {
return 0;
}
int leftHeight = TreeHeight(root->left);
int rightHeight = TreeHeight(root->right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
3.3.获取当前树的第k层节点个数
左子树的k-1层节点个数+右子树的k-1层节点个数,当k-1为0时,返回0,k-1为1时,返回1。
int TreeKLevel(BTNode* root, int k) {
if (root == NULL) {
return 0;
}
if (k == 1) {
return 1;
}
int leftk = TreeKLevel(root->left, k - 1);
int rightk = TreeKLevel(root->right, k - 1);
return leftk + rightk;
}
3.4.查找值为x的节点
先从当前节点开始,如果是值为x的节点,直接返回,如果不是,再从节点的左子树和右子树中查找,遇到空节点返回NULL。
BTNode* TreeNodeFind(BTNode* root, BTDataType x) {
if (root == NULL) {
return NULL;
}
if (root->data == x) {
return root;
}
BTNode* left = TreeNodeFind(root->left, x);
if (left) {
return left;
}
BTNode* right = TreeNodeFind(root->right, x);
if (right) {
return right;
}
return NULL;
}
3.5.判断完全二叉树
判断完全二叉树的思路和二叉树的层序遍历一样需要借助队列来实现,但和层序遍历不一样的是,判断完全二叉树需要将空节点入队,如果是二叉树时,存放空节点的队列节点后面的队列节点存放的也都为空,如果存放空节点的队列节点后面有队列节点存放的是非空节点时,就不是完全二叉树。
bool LevelTree(BTNode* root) {
Queue q;
QueueInit(&q);
if (root != NULL) {
QueuePush(&q, root);
}
//将完全二叉树所有的节点包括空节点都入队,知道出队遇到空节点
while (!QueueIsEmpty(&q)) {
BTNode* front = GetQueueFront(&q);
QueuePop(&q);
if (front == NULL) {
break;
}
else {
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
}
//从空节点开始遍历队列,如果遇到不是空节点就不是完全二叉树
while (!QueueIsEmpty(&q)) {
BTNode* front = GetQueueFront(&q);
QueuePop(&q);
if (front) {
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
3.6.测试结果
四.总结
在我们学习二叉树时,对于指针,以及递归的过程需要较高的理解,二叉树的函数实现大多都用到了递归思想,在学习二叉树时一定要尝试多画递归过程图,如果有哪里不理解的也可以看看其他大佬写的关于二叉树的博客。
以上就是关于二叉树的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!