文章目录
树是什么?
树是一种非线性的数据结构,它是由n个有限结点组成一个具有层次关系的集合,如果n=0时,称为空树。
把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
树的概念
在任意一棵非空的树中应满足:
1.有且仅有一个特殊的结点,称为根结点。
2.在树形结构中,子树之间不能有交集,否则就不是树形结构。
3.除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。
4.显然,树的定义是递归的,即在树的定义中又用到了自身,树是一种递归的数据结构。树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点:
1.树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
2.树中所有结点可以有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)棵互不相交的树的集合称为森林;
什么是二叉树?
二叉树是另一种树形结构,二叉树也以递归的形式定义。二叉树是n (n≥0) 个结点的有限集合。二叉树的两种情况:
1.为空二叉树,即n=0。
2.由一个根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一棵二叉树。
二叉树的概念
- 二叉树的每个结点至多只有两棵子树( 即二叉树中不存在度大于2的结点)。
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。
对于任意的二叉树都是由以下5种基本形态复合而成的:
二叉树的性质
特殊的二叉树
- 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,并且除叶子结点之外的每个结点度数均为2,则这个二叉树就是满二叉树。反过来说,如果一个二叉树的层数为K,且结点总数是 (2的k次方-1),则它就是满二叉树。
- 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
链式二叉树的实现
接下来我们按照下面的图片中的二叉树结构来进行代码实现:
二叉树的类型创建
typedef int BTDatetype;
//定义一个结构体,data存储数据
//结构体指针left指向左子树,结构体指针right指向右子树
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDatetype data;
}BTNode;
二叉树如何创建一个结点
//生成结点的函数
BTNode* BuyNode(BTDatetype x)
{
//空间需要我们malloc申请,申请的空间大小就是结构体的大小
BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
assert(tmp);
//左孩子初始化为空,右孩子初始化为空
tmp->left = NULL;
tmp->right = NULL;
tmp->data = x;
return tmp;
}
BTNode* CreatBinaryTree()
{
//这里我们申请6个结点出来,方便后面使用
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
//这时候结点之间还没有联系
//下面就是按照"链式二叉树下面的图"的结构链接起来的二叉树
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
//最后把第一个结点,也就是根结点返回
return node1;
}
链式二叉树的基本接口函数
//前序遍历
void Preorder(BTNode* root);
//中序遍历
void Inorder(BTNode* root);
//后序遍历
void Postorder(BTNode* root);
//层序遍历
void Levelorder(BTNode* root);
//求二叉树的结点个数
int TreeSize(BTNode* root);
//求二叉树的叶子结点个数
int TreeLeafSize(BTNode* root);
//求二叉树的第k层的结点个数
int TreeKLevel(BTNode* root, int k);
//求二叉树的深度
int TreeDepth(BTNode* root);
//二叉树的查找数据
BTNode* TreeFind(BTNode* root, BTDatetype x);
//判断这个二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);
//二叉树的销毁
void BinaryTreeDestory(BTNode* root);
二叉树的遍历
我们先来了解一下二叉树是如何进行以下几种遍历方式的,以及遍历的结果,画图详解:
//前序遍历
void Preorder(BTNode* root);
//中序遍历
void Inorder(BTNode* root);
//后序遍历
void Postorder(BTNode* root);
//层序遍历
void Levelorder(BTNode* root);
前序遍历
通过递归来完成遍历,先打印根结点的数据,再递归根结点的左子树,左子树递归完,再递归右子树,这里用眼睛看比较容易混乱,建议画一下递归展开图,会非常清晰。
void Preorder(BTNode* root) //前序遍历顺序 根->左子树->右子树
{
//如果结点为NULL 就打印#号来代替 防止意外使用NULL结点
if (root == NULL)
{
printf("# ");
return;
}
printf("%d ", root->data); //先打印根 根不是空 就去找根的左子树
Preorder(root->left); //直到为空,再去找右子树,直到为空
Preorder(root->right);
}
中序遍历
和前序遍历类似,不过中序遍历是先递归根结点的左子树进行打印,然后根,最后再去递归右子树,形成左子树->根->右子树的遍历方式。
void Inorder(BTNode* root) //中序遍历顺序 左子树->根->右子树
{
if (root == NULL)
{
printf("# ");
return;
}
Inorder(root->left);
printf("%d ", root->data);
Inorder(root->right);
}
后序遍历
和前面两个遍历方式类似,后序遍历是先递归访问左子树,然后右子树,最后是根。
void Postorder(BTNode* root) //后序遍历顺序 左子树->右子树->根
{
if (root == NULL)
{
printf("# ");
return;
}
Postorder(root->left);
Postorder(root->right);
printf("%d ", root->data);
}
层序遍历
这里我们需要借用下对列来实现层序遍历,因为队列的特性是先进先出,所以要借助队列完成层序遍历就需要先添加这个结点进入队列,然后打印,如果这个结点的左右子树都不为空就都添加进队列,凭借队列的先进先出,先进去左子树,后进去右子树,然后出队列时一个一个访问,这样就形成了层序遍历。
void Levelorder(BTNode* root)
{
Queue q;
QueueInit(&q); //队列中数据的类型是BTNode* 二叉树的结点指针类型
if (root != NULL)//结点不等于空就往队列中添加数据
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q)) //队列不为空
{
BTNode* front = QueueFront(&q);
printf("%d ", front->data);
QueuePop(&q);
if (front->left != NULL)//如果这个结点的左右子树不为空就添加进队列,为空就不添加
{ //出队列出一个数据就添加这个数据的左右子树,形成层序遍历
QueuePush(&q, front->left);
}
if (front->right != NULL)
{
QueuePush(&q, front->right);
}
}
printf("\n");
QueueDestroy(&q);
}
求二叉树的结点个数
利用分治的思想,先递归根结点的左子树,然后把左子树这个结点也看作小的一颗二叉树,然后再递归它的左子树,一直到NULL为止,然后把右子树也看作一颗小的二叉树,再一直递归它的右子树,最后返回这个结点的左子树个数+右子树的个数+1,一直回到根结点,最终得到的返回值就是这颗二叉树的所有结点个数了。
int TreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
//加1是自身结点 即使没有左子树和右子树也有自身结点1
return TreeSize(root->left)+ TreeSize(root->right)+1;
}
求二叉树的叶子结点个数
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
求二叉树第k层的结点个数
int TreeKLevel(BTNode* root, int k)
{
assert(k >= 1);
if (root == NULL)
{
return 0;
}
if (k == 1) //转换为子问题 求k层的结点数,就是求根结点左子树的
{ //k-1层 + 右子树的k-1层,层层递归
return 1;
}
return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
求二叉树的深度
一颗二叉树的深度,是叶子结点距离根结点最远的那个结点,这个距离就是二叉树的深度或高度。
int TreeDepth(BTNode* root)
{
int left, right, tmp;
if (root != NULL)
{
left = TreeDepth(root->left); //计算左子树深度
right = TreeDepth(root->right); //计算右子树深度
tmp = left > right ? left : right; //比较大小
return tmp + 1; //返回大的深度+1;
}
else
return 0;
}
二叉树的查找数据
BTNode* TreeFind(BTNode* root, BTDatetype x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x) //如果root直接找到了就直接返回
{
return root;
}
BTNode* left = TreeFind(root->left, x); //找不到就去左子树找
if (left!=NULL)
{
return left;
}
BTNode* right = TreeFind(root->right, x);//找不到就去右子树找
if (right!=NULL)
{
return right;
}
return NULL; //最后找不到返回NULL
}
判断这个二叉树是否是完全二叉树
判断是否是完全二叉树的方法:
1.根据完全二叉树的性质,如果是完全二叉树的话,那么按照层序遍历的方式进行遍历,所有的结点都是连续的,不会存在中间有NULL结点,NULL结点只会在末尾出现,并且一出现NULL,那后面就都是NULL结点了。
2.按照 1 的描述,如果出现了NULL结点之后,后面的结点如果还有非空的结点,那么这棵二叉树就不是完全二叉树。
这里我们需要借用队列来完成,利用队列的先进先出特性,先把整棵树的所有结点依次按照层序遍历的方式添加进队列中,然后进行上面的方法对比判断是否是完全二叉树。
int BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root != NULL)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front != NULL) //只要这个结点不为空 就添加它的左右子树进
{ //队列,左右子树不管为不为空 为空也添加进去
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
else
{
break; //遇到空结点了 就跳出循环
}
}
//1.如果后面的结点全是空就是完全二叉树
//2.如果后面有非空的结点就不是完全二叉树
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front != NULL)
{
QueueDestroy(&q);
return false;
}
}
return true;
QueueDestroy(&q);
}
二叉树的销毁
销毁这里我们采用后序的方式来进行销毁,先销毁左子树->右子树->根。
//用后序的方式
void BinaryTreeDestory(BTNode* root)
{
if (root == NULL)
{
return;
}
BinaryTreeDestory(root->left);
BinaryTreeDestory(root->right);
free(root);
}
所有接口函数的运行结果
int main()
{
BTNode* root = CreatBinaryTree();
Preorder(root); //前序遍历
printf("\n");
Inorder(root); //中序遍历
printf("\n");
Postorder(root); //后序遍历
printf("\n");
printf("二叉树的结点个数:%d\n", TreeSize(root));
printf("二叉树的叶子结点个数:%d\n", TreeLeafSize(root));
printf("二叉树第k层的结点个数:%d\n", TreeKLevel(root,2));
printf("二叉树第k层的结点个数:%d\n", TreeKLevel(root,3));
printf("二叉树的深度:%d\n", TreeDepth(root));
Levelorder(root); //层序遍历
int ret=BinaryTreeComplete(root);//判断完全二叉树 0为假,非0为真
printf("是完全二叉树吗:%d\n", ret);
BinaryTreeDestory(root); //销毁二叉树
root = NULL;
return 0;
}