树和二叉树
树定义:
专业定义【递归定义】
如果是一个非空树,则必须满足以下两点:
1、有且仅有一个称为根的结点
2、有若干个互不相交的子树,这些子树本身也是一棵树
通俗的定义【非递归定义】
1、树由结点和边组成
2、没有父结点的结点称为根节点
3、每个结点有且仅有一个父结点但可以有多个子结点
一些专业术语
结点
父结点
子结点
子孙
堂兄弟
深度:
从根节点到叶子节点的层数称为深度
叶子结点(终端结点):
没有子结点的结点
度:
某结点的子树个数称为该结点的度
分类
一般树
可以有任意棵子树的树
二叉树
每个非叶子节点(非终端结点)的子树个数最多为两个,且子树位置不可改变的树
分类:
一般二叉树
满二叉树
每个非叶子节点都有且仅有两个子树(左子树和右子树)
完全二叉树
如果只删除了满二叉树最底层最右边的连续的若干个结点,这样的二叉树叫完全二叉树
森林
N个互不相交的树的集合
存储
二叉树的存储
连续二叉树【完全二叉树】
优点:
查找某个父结点和子结点(也包括判断有没有)
缺点:
耗用内存空间过大
链式存储
一般树的存储
双亲表示法
即使用数组(这不是传统意义上的数组,而是利用结构体造出的数组)来实现一般树的存储
举个例子,如下图所示的二叉树
双亲表示法的存储如下图所示
最左边的数字是各个结点元素的下标,左边这一列是结点数据(其顺序不固定),右边这一列是结点元素的父结点的下标(其中,根结点处为-1)
这种方法,求父结点方便,求子结点困难
孩子表示法
还是举例说明,如下图所示
同样是用数组(非传统)来实现一般树的存储,具体格式如下所示
每个结点的右边是它的子结点,没有子结点的为空
这种方法,求子结点方便,求父结点困难
双亲孩子表示法
如下图所示
前面已经说过,数组元素的位置不固定,最左边是元素下标,接着是元素,中间是结点元素的父结点下标,最右边是子结点,相当于把上面两个合并了
这种方法,求父结点和子结点都很方便
二叉树表示法
把一个普通树转化成二叉树来存储
具体转化方法
设法保证任意一个结点的
左指针域指向它的第一个孩子
右指针域指向它的下一个兄弟
只要能满足此条件,就可以把一个普通树转化为二叉树来存储
森林的存储
先把森林转化为二叉树,再存储二叉树
二叉树操作
遍历(访问)
所谓先序、中序、后序,是指对根结点的访问
先序遍历
先访问根结点
再先序遍历左子树
再先序遍历右子树
中序遍历
先中序遍历左子树
再访问根结点
再中序遍历右子树
后序遍历
先中序遍历左子树
再中序遍历右子树
再访问根结点
已知两种遍历序列求原始二叉树
通过先序和中序 或者 中序和后序我们可以
还原出原始的二叉树
但是通过先序和后序是无法还原出原始的二叉树的
下面举例说明(已知先序中序求后序),
先序遍历为ABCDEFGH
中序遍历为BDCEAFGH
求后序遍历次序
由先序次序可知,A为根节点,则由中序次序可知,A的左边是它的左子树,A的右边是它的右子树,再由先序次序,可知此二叉树的左子树(须清楚左子树或右子树也是树)的根节点为B,因为B的左边没有结点,所以B没有左子树,B的右子树结点分别为D、C、E,由由先序次序可知,在B的右子树中,C为根节点,再看中序次序,C的左边是D,右边是E,所以,D是C的左子树,E是C的右子树,最终得到该二叉树的左子树如下图所示
接下来看A的右子树,共有3个结点,分别是F、G、H,由先序次序可知,F为A的右子树的根结点,由中序次序知,F没有左子树,但有一个结点为G的右子树,再看先序次序,G有一个结点为H的右子树,最后根据中序次序,G没有左子树,只是有一个根结点为H的右子树,因为H往后没有结点,所以G的右子树即为一个结点H
综上,得出原始的二叉树如下图所示
最终根据二叉树结构得该二叉树的后序遍历次序为DECBHGFA
再举一个例子(已知中序后序求先序)
中序遍历为BDCEAFGH
后序遍历为DECBHGFA
求先序遍历
由后序次序可知,A为根结点,再由中序次序知BDCE为A的左子树,FGH为A的右子树,在A的左子树的各个结点B、D、C、E中,由后序次序知B为A的左子树的根结点,再由中序次序知B没有左子树,B的右子树为DCE,再根据后序次序知C为B的右子树的根结点,再根据中序次序,知D和E分别为C的左子树和右子树,此时该二叉树的左子树已经得到,如下图所示
同理在后序中,由HGF次序知F为A的右子树的根结点,再由中序次序,知,G为F的右子树根结点,H为G的右子树根结点,最终得出二叉树如下图所示
应用
树是数据库中数据组织的一种重要形式
操作系统子父进程的关系本身就是一棵树
面向对象语言中类的继承关系本身也是一棵树
哈夫曼树
下面举个例子,构造一个链式二叉树,并输出它的先序、中序和后序的顺序输出,二叉树如下图所示:
其先序、中序及后序遍历的次序为
先序 ABCDE
中序 BADEC
后序 BEDCA
程序如下:
# include <stdio.h>
# include <malloc.h>
typedef struct BTNode
{
char data;
struct BTNode * pLchild; //p是指针 L是左 child是孩子
struct BTNode * pRchild;
}BT, * PBT;
PBT CreateBTree(void); //构造一个二叉树
void PreTraverseBTree(PBT pT); //先序
void InTraverseBTree(PBT pT); //中序
void PostTraverseBTree(PBT pT); //后序
int main(void)
{
PBT pT = CreateBTree();
printf("先序遍历二叉树:\n");
PreTraverseBTree(pT);
printf("\n\n");
printf("中序遍历二叉树:\n");
InTraverseBTree(pT);
printf("\n\n");
printf("后序序遍历二叉树:\n");
PostTraverseBTree(pT);
printf("\n\n");
return 0;
}
void PreTraverseBTree(PBT pT)
{
if (NULL != pT)
{
printf("%c ", pT->data);
if (NULL != pT->pLchild)
{
PreTraverseBTree(pT->pLchild);
}
if (NULL != pT->pRchild)
{
PreTraverseBTree(pT->pRchild);
//pT->pLchild可以代表整个左子树
}
}
}
void InTraverseBTree(PBT pT)
{
if (NULL != pT)
{
if (NULL != pT->pLchild)
{
InTraverseBTree(pT->pLchild);
}
printf("%c ", pT->data);
if (NULL != pT->pRchild)
{
InTraverseBTree(pT->pRchild);
//pT->pLchild可以代表整个左子树
}
}
}
void PostTraverseBTree(PBT pT)
{
if (NULL != pT)
{
if (NULL != pT->pLchild)
{
PostTraverseBTree(pT->pLchild);
}
if (NULL != pT->pRchild)
{
PostTraverseBTree(pT->pRchild);
//pT->pLchild可以代表整个左子树
}
printf("%c ", pT->data);
}
}
PBT CreateBTree(void)
{
PBT pA = (PBT)malloc(sizeof(struct BTNode));
PBT pB = (PBT)malloc(sizeof(struct BTNode));
PBT pC = (PBT)malloc(sizeof(struct BTNode));
PBT pD = (PBT)malloc(sizeof(struct BTNode));
PBT pE = (PBT)malloc(sizeof(struct BTNode));
pA->data = 'A';
pB->data = 'B';
pC->data = 'C';
pD->data = 'D';
pE->data = 'E';
pA->pLchild = pB;
pA->pRchild = pC;
pB->pLchild = pB->pRchild = NULL;
pC->pLchild = pD;
pC->pRchild = NULL;
pD->pLchild = NULL;
pD->pRchild = pE;
pE->pLchild = pE->pRchild = NULL;
return pA;
}
输出结果为:
【所有代码均在windows系统下C-Free5.0下运行通过】
(如有错误,敬请指正)