一.树的定义
1.什么是树
有且只有一个根节点,有若干个互不相交的子树。
2.专业术语
如上图:我们解释父节点、子节点、兄弟节点、堂兄弟节点。
父节点:A为父节点
子节点:B、C是A的子节点
兄弟节点:B和C是兄弟节点
堂兄弟节点:D与E是堂兄弟节点
深度:树中节点的最大层次(从根节点到最底层节点的层数,图中深度为3)
叶子节点:没有子节点的节点(B、E是叶子节点)
度:子节点的个数
二.树的分类
1.一般树
一般树:任意一个节点的子节点个数都不受限制。
2.二叉树
二叉树:任意一个节点的子节点个数最多为2个,且子节点位置不可更改。
3.森林
森林:n个互不相交的树的集合。
扩展:二叉树的分类
1.一般二叉树
2.满二叉树:在不增加树层次的前提下,不可添加新节点
3.完全二叉树:将满二叉树最底层最右边的连续若干个节点
如图,是一个满二叉树:
变成完全二叉树,可以不删除节点(完全二叉树包含满二叉树),也可以删除 G、GE、GEF、GEFD.
三.树的存储
1.连续存储:
连续存储必须是完全二叉树的形式
为什么呢?
如下图,一颗普通树:
假设我们使用层次的关系(从左到右,从上到下的关系来存储)
该树在数组中的存储结构如下:
我们根据此数组将树还原:
到底是这种形式呢?
还是这种形式呢?
有歧义,不能确定节点的逻辑关系。
也就是,如果一棵树不是完全二叉树的形式,我们不能根据连续的存储结构来还原一棵树。
所以,连续的存储结构必须是完全二叉树的形式。
我们可以如下存储:
至此,我们总结一下连续结构存储完全二叉树的优缺点:
优点:
1.查找某个节点的父节点与子节点很快
2.可以通过编号确定节点在第几层
缺点:
浪费空间(如上面存储,还得存储空节点)
2.链式存储
我们将树的节点分为3部分构成:
pLeft指向左子树、pRight指向右子树、data存放节点数据。
如下图:
我们可以使用 先序遍历、中序遍历、后序遍历 遍历这棵树。
链式存储相对于连续存储来说,更节省空间,所以使用范围广。
3.非链式存储
对于下面树的存储:
1.双亲表示法
A:-1(代表A是根节点)
B:0(代表B的父节点是A)
C:0(代表C的父节点是A)
D:1(代表D的父节点是B)
E:1(代表E的父节点是B)
特点:求父节点很方便,但是求子节点麻烦。
2.孩子表示法
特点:求子节点很方便,但是求父节点麻烦。
3.双亲-孩子表示法
特点:求子节点很方便,求父节点也很方便,但是浪费空间。
4.二叉树表示法
对于一般树的存储,我们可以使用双亲表示法、孩子表示法、双亲-孩子表示法。
我们也可以将一棵树转换成二叉树来存储。
因为现在,我们对二叉树的算法比较成熟,对普通树的算法不成熟。所以我们通常将普通树转换成二叉树来存储,便于以后的操作。
我们将一颗普通树转变为二叉树来存储的规则是:左指针域指向第1个孩子,右指针域指向兄弟。
总结:
连续存储结构:浪费空间(必须是完全二叉树),但是查找速度快
链式存储结构:节约空间,查找速度还可以
非链式存储结构:最浪费空间
我们最常用的存储结构是:链式二叉树
四.树的操作
1.遍历
1.1前序遍历
前序遍历的顺序是:根节点-左子树-右子树
上面图片树的前序遍历为:A-B-D-E-C-F
1.2中序遍历
中序遍历的顺序是:左子树-根节点-右子树
上面图片树的前序遍历为:D-B-E-A-F-C
1.3后序遍历
后序遍历的顺序是:左子树-右子树-根节点
上面图片树的前序遍历为:D-E-B-F-C-A
2.根据遍历结果还原一棵树
我们可以根据:
先序遍历+中序遍历—>还原一棵树
中序遍历+后序遍历—>还原一棵树
但是不能:
前序遍历+后序遍历—>还原一棵树
因为:
前序遍历可以确定根节点,但是不能确定左子树、右子树。
后序遍历可以确定根节点,但是不能确定左子树、右子树。
中序遍历可以确定左子树、右子树,但是不能确定根节点。
所以:
先序遍历或后序遍历只有和中序遍历结合,才能确定一棵树的根节点、左子树、右子树。
我们在根据 先序+中序—>还原一棵树 或 中序+后序—>还原一棵树 的时候,有如下规律:
先序:最先出现的是根节点
后序:最后出现的是根节点
我们根据先序和后序的规律,再结合中序,就能轻松的还原一棵树。
具体的练习就不写了,其实并不难。
五.树的应用
1.是数据库中数据组织的一种重要方式(如图书管理数据库)
2.操作系统子父进程的关系就是一棵树。
3.面向对象中类的继承关系
等等…
六.编码实战
我们创建下面图中的这棵树,并且实现它的前序遍历,中序遍历、后序遍历。
#include <stdio.h>
#include <stdlib.h>
struct BTNode
{
char data;
struct BTNode * PTLchild;
struct BTNode * PTRchild;
};
struct BTNode *createBTree();
void preTraverseBTree();
void inTraverseBTree();
void postTraverseBTree();
int main()
{
struct BTNode * pT=createBTree();
preTraverseBTree(pT);
inTraverseBTree(pT);
postTraverseBTree(pT);
return 0;
}
//创建一棵树
struct BTNode *createBTree()
{
struct BTNode * pA=(struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pB=(struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pC=(struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pD=(struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pE=(struct BTNode *)malloc(sizeof(struct BTNode));
pA->data='A';
pB->data='B';
pC->data='C';
pD->data='D';
pE->data='E';
pA->PTLchild=pB;
pA->PTRchild=pC;
pB->PTLchild=NULL;
pB->PTRchild=NULL;
pC->PTLchild=pD;
pC->PTRchild=NULL;
pD->PTLchild=NULL;
pD->PTRchild=pE;
pE->PTLchild=NULL;
pE->PTRchild=NULL;
return pA;
}
//先序遍历
void preTraverseBTree(struct BTNode * pT)
{
if(pT!=NULL)
{
printf("%c\n",pT->data);
if(pT->PTLchild!=NULL)
{
preTraverseBTree(pT->PTLchild);
}
if(pT->PTRchild!=NULL)
{
preTraverseBTree(pT->PTRchild);
}
}
}
//中序遍历
void inTraverseBTree(struct BTNode * pT)
{
if(pT!=NULL)
{
if(pT->PTLchild!=NULL)
{
inTraverseBTree(pT->PTLchild);
}
printf("%c\n",pT->data);
if(pT->PTRchild!=NULL)
{
inTraverseBTree(pT->PTRchild);
}
}
}
//后序遍历
void postTraverseBTree(struct BTNode * pT)
{
if(pT!=NULL)
{
if(pT->PTLchild!=NULL)
{
postTraverseBTree(pT->PTLchild);
}
if(pT->PTRchild!=NULL)
{
postTraverseBTree(pT->PTRchild);
}
printf("%c\n",pT->data);
}
}