树
1. 树的基本知识
1.1 专业术语
- 节点: 树上每一个单独的个体就是节点;
- 父节点(双亲节点): 上一层与当前节点相连的节点就是父节点,除根节点外有且只有一个;
- 子节点: 下一层与当前节点相连的节点就是子节点,可以有任意个;
- 堂兄弟: 同一层但父节点不同的两个节点互为堂兄弟;
- 深度: 从根节点到最底层的层数称之为深度,也叫树的高度;
- 叶子节点: 没有子节点的节点;
- 非终端节点: 有孩子的节点;
- 根节点: 树中唯一一个没有父节点的节点,根节点算树的第一层;
- 度: 子节点的个数称为度。
1. 2 树的定义
- 树是由节点和边组成;
- 树有且只有一个根节点;
- 树有若干个互不相交的子树,这些子树本身也是一棵树;
- 除根节点外每个节点都只有一个父节点,但可以有很多个子节点;
1.3 树的分类
- 一般树: 任意一个节点的个数都不受限制;
- 二叉树: 任意一个节点的子节点最多有两个,且子节点分左节点和右节点;
- 满二叉树: 每一层节点树都是最大树,在不增加树的层数的前提下,无法再多添加节点的二叉树就是满二叉树;
- 完全二叉树: 删除满二叉树最底层最左边的连续若干个节点,得到的树就是完全二叉树。
- 森林: n个互不相交的树的集合。
2. 树的存储
2.1 二叉树存储
2.1.1 连续存储
二叉树的连续存储,需要把二叉树当成完全二叉树或者满二叉树来分配内存,便于之后的操作,以及线性存储结构对树结构的还原。
- 优点: 便于查找某个节点的父节点和子节点;
- 缺点: 消耗内存空间过大。
具体实现:
- 直接创建满足长度要求的数组即可(满二叉树的节点数等于2的层数次幂减1);
- 二叉树的根节点占据下标为零的位置;
- 每个左孩子所占下标为双亲节点下标二倍加一;
- 每个右孩子所占下标为双亲节点下标二倍加二。
2.1.2 链式存储
链式存储需要分别指向左孩子和右孩子的两个指针,这样就可以构建树的结构了,必要的情况下可以增加指向父节点的指针。节点的结构体代码如下:
typedef struct BTNode
{
char data;
BTNode * Lchild;//左孩子
BTNode * Rchild;//右孩子
} * pBTNode;
2.2 一般树的存储
2.2.1 双亲表示法
双亲表示法中每个节点存放父节点的下标(连续存储)或地址(链式存储)。
- 优点: 便于查找某个节点的父节点;
- 缺点: 找子节点比较麻烦;
结构体代码:
typedef struct BTNode
{
char data;
BTNode * D;//连续存储用int类型,用来查找父节点
} * pBTNode;
2.2.2 孩子表示法
创建一个链表数组,将每个元素都作为一个链表的表头,然后把其所有的孩子通过链表连起来。
- 优点: 便于查找某个节点的子节点;
- 缺点: 找父节点比较麻烦;
结构体代码:
typedef struct BTNode
{
char data;
BTNode* next;
}BTNODE,*PBTNode;
//使用的时候创建一个数组BTNODE B[元素个数];
//然后再把每个元素的孩子连在其后面。
2.2.3 孩子双亲表示法
在孩子表示法的基础上,在数组元素中增加一个成员,用来存放其父节点的下标,这样就可以轻松找到其父节点了。
结构体代码:
typedef struct BTNode
{
char data;
int D;//存放父节点的下标
BTNode* next;
}BTNODE,*PBTNode;
2.2.4 二叉树表示法
把一个普通树转化为二叉树存储。
方法: 设法保证任意一个节点的左指针指向其第一个孩子,右指针指向其的堂兄弟,即可转化为二叉树,这个二叉树没有右子树。
2.3 森林的存储
森林的存储需要先将森林转化为二叉树,转化过程可以把每一棵树的根节点互相当作兄弟节点,然后在用一般树二叉树表示法来进行转换即可。
3. 二叉树的遍历
3.1 先序遍历
顺序:
- 先访问根节点;
- 在访问左子树;
- 在访问右子树。
代码:
void preorder(BTNode * pbt)
{
printf("%c",pbt->data);
if(pbt->Lchild != NULL)
preorder(pbt->Lchild);
if(pbt->Rchild != NULL)
preorder(pbt->Rchild);
return;
}
3.2 中序遍历
顺序:
- 先访问左子树;
- 在访问根节点;
- 在访问右子树。
代码:
void infix(BTNode * pbt)
{
if(pbt->Lchild != NULL)
infix(pbt->Lchild);
printf("%c",pbt->data);
if(pbt->Rchild != NULL)
infix(pbt->Rchild);
return;
}
3.3 后续遍历
顺序:
- 先访问左子树;
- 在访问右子树;
- 在访问根节点。
代码:
void epilogue(BTNode * pbt)
{
if(pbt->Lchild != NULL)
epilogue(pbt->Lchild);
if(pbt->Rchild != NULL)
epilogue(pbt->Rchild);
printf("%c",pbt->data);
return;
}
3.4 已知遍历顺序求二叉树
必须知道中序遍历顺序和先序遍历顺序或者后序遍历顺序才能够求出二叉树的结构,因为方法过于简单且不容易仅仅用文字表述,所以不做解释,需要学习请自行百度。
5. 树的应用
- 树是数据库中数据组织的一种重要形式;
- 操作系统父子进程的关系本身就是树;
- 面向对象中类的继承问题;
- 哈夫曼树。
哈夫曼树: