树的定义
树(Tree):是n(n=0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:
- 有且仅有一个特定的称为根(Root)的结点;
- 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、…、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
结点分类:
结点拥有的子树数称为结点的度(Degree)。度为0的结点称为叶结点(Leaf)或终端结点;度不为0的结点称为非终端结点或分支结点。除根结点之外,分支结点也称为内部结点。树的度是树内各结点的度的最大值。
结点间关系:
- 结点的子树的根称为该结点的孩子(Child),该结点称为孩子的双亲(Parent)。
- 同一个双亲的孩子之间互称兄弟(Sibling)。
- 结点的祖先是从根到该结点所经分支上的所有结点。
- 以某结点为根的子树中的任一结点都称为该结点的子孙。
树的其他相关概念:
- **结点的层次(Level)**从根开始定义起,根为第一层,根的孩子为第二层。
- 双亲在同一层的结点互为堂兄弟。
- 树中结点的最大层次称为树的深度(Depth)或高度。
- 树中结点的各子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。
- 森林(Forest)是m(m>0)棵互不相交的树的集合。
线性结构和树结构的不同:
树的抽象数据类型
ADT树(tree)
Data
树是由一个根结点和若干棵子树构成。树中结点具有相同数据类型及层次关系。
Operation
InitTree(*T):构造空树T。
DestroyTree(*T):销毁树T。
CreateTree(*T,definition):按definition中给出树的定义来构造树。
ClearTree(*T):若树T存在,则将树T清为空树。
TreeEmpty(T):若T为空树,返回true,否则返回false。
TreeDepth(T):返回T的深度。
Root(T):返回T的根结点。
Value(T,cure):cure是树T中一个结点,返回此结点的值。
Assign(T,cur_e,value):给树T的结点cur_e赋值为value。
Parent(T,cure):若cur_e是树T的非根结点,则返回它的双亲,否则返回空。
Leftchild(T,cure):若cure是树T的非叶结点,则返回它的最左孩子,否则返回空。
Rightsibling(T,cure):若cure有右兄弟,则返回它的右兄弟,否则返回空。
InsertChild(*T,*p,i,c):其中p指向树T的某个结点,i为所指结点p的度加上1,非空树c与T不相交,操作结果为插入c为树T中p指结点的第i棵子树。
DeleteChild(*T,*p,i):其中p指向树T的某个结点,i为所指结点p的度,操作结果为删除T中p所指结点的第i棵子树。
endADT
树的存储结构
双亲表示法:在每个结点中,附设一个指示器指示其双亲结点到链表中的位置。
孩子表示法:把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。
双亲孩子表示法:
孩子兄弟表示法:任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
二叉树的定义
二叉树 (Binary Tree)是n(n=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
二叉树特点:
- 每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点
- 左子树和右子树是有顺序的,次序不能任意颠倒。
- 即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。
特殊二叉树:
- 所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。
- 在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
- 对一棵具有n个结点的二叉树按层序编号,如果编号为i(1<i<n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
二叉树的性质
性质1:在二叉树的第i层上至多有2i-1个结点(i>1)。
性质2:深度为k的二叉树至多有2k-1个结点(k>1)。
性质3:对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
性质4:具有n个结点的完全二叉树的深度为[log2n]+1([x]表示不大于x的最大整数)。
性质5:如果对一棵有n个结点的完全二叉树(其深度为[log2n]+1)的结点按层序编号(从第1层到第[log2n]+1层,每层从左到右),对任一结点i(1<i<n)
- 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2]加粗样式。
- 如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
- 如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。
二叉树的存储结构
二叉树顺序存储结构:用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系,比如双亲与孩子的关系,左右兄弟的关系等。顺序存储结构一般只用于完全二叉树。
二叉链表:二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域,我们称这样的链表叫做二叉链表。
遍历二叉树
二叉树遍历原理:二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。
前序遍历:规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
中序遍历:规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
后序遍历:规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。
层序遍历:规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
遍历算法(使用递归):
/* 初始条件: 二叉树T存在 */
/* 操作结果: 前序递归遍历T */
void PreOrderTraverse(BiTree T)
{
if(T==NULL)
return;
printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */
PreOrderTraverse(T->lchild); /* 再先序遍历左子树 */
PreOrderTraverse(T->rchild); /* 最后先序遍历右子树 */
}
/* 初始条件: 二叉树T存在 */
/* 操作结果: 中序递归遍历T */
void InOrderTraverse(BiTree T)
{
if(T==NULL)
return;
InOrderTraverse(T->lchild); /* 中序遍历左子树 */
printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */
InOrderTraverse(T->rchild); /* 最后中序遍历右子树 */
}
/* 初始条件: 二叉树T存在 */
/* 操作结果: 后序递归遍历T */
void PostOrderTraverse(BiTree T)
{
if(T==NULL)
return;
PostOrderTraverse(T->lchild); /* 先后序遍历左子树 */
PostOrderTraverse(T->rchild); /* 再后序遍历右子树 */
printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */
}
二叉树遍历的性质:
- 已知前序遍历序列和中序遍历序列,可以唯一确定一棵二叉树。
- 已知后序遍历序列和中序遍历序列,可以唯一确定一棵二叉树。
- 已知前序和后序遍历,是不能确定一棵二叉树的。
线索二叉树
线索二叉树原理:指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树(Threaded Binary Tree)。
线索化:对二叉树以某种次序遍历使其变为线索二叉树的过程称做是线索化,线索化的过程就是在遍历的过程中修改空指针的过程。
树、森林与二叉树的转换
树转换为二叉树:加线、去线、层次调整。
森林转换为二叉树:把每个树转换为二叉树,第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来。当所有的二叉树连接起来后就得到了由森林转换来的二叉树。
二叉树转换为森林:从根结点开始,若右孩子存在,则把与右孩子结点的连线删除,再查看分离后的二叉树,若右孩子存在,则连线删除……,直到所有右孩子连线都删除为止,得到分离的二叉树。
森林的遍历:
- 前序遍历:先访问森林中第一棵树的根结点,然后再依次先根遍历根的每棵子树,再依次用同样方式遍历除去第一棵树的剩余树构成的森林。
- 后序遍历:是先访问森林中第一棵树,后根遍历的方式遍历每棵子树,然后再访问根结点,再依次同样方式遍历除去第一棵树的剩余树构成的森林。
赫夫曼树及其应用
赫夫曼树:
- 根据给定的n个权值{W1,W2.…,Wn}构成n棵二叉树的集合F={T1,T2…,Tn},其中每棵二叉树Ti,中只有一个带权为w,根结点,其左右子树均为空。
- 在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
- 在F中删除这两棵树,同时将新得到的二叉树加入F中。
- 重复2和3步骤,直到F只含一棵树为止。这棵树便是赫夫曼树。
赫夫曼编码:
一般地,设需要编码的字符集为{d1,d2…dn},各个字符在电文中出现的次数或频率集合为{W1,W2.…,WN},以d1,d2…dn作为叶子结点,以W1,W2.…,WN作为相应叶子结点的权值来构造一棵赫夫曼树。规定赫夫曼树的左分支代表0,右分支代表1,则从根结点到叶子结点所经过的路径分支组成的0和1的序列便为该结点对应字符的编码,这就是赫夫曼编码。
赫夫曼编码示例:
假设六个字母的频率为A27,B8,C15,D15,E30,F5,频率加起来为100%,则赫夫曼编码如下