一、 树的定义
树是n个数据元素的有限集(记为T),对任意一棵树T有:
1. 存在唯一一个称为根的数据元素;
2. 当n>1时,其他数据元素可分为m(m>0)个互不相交的有限集T1,T2,…Tm,其中每个集合Ti(i=1,2,…,m)本身又是一棵树,并称树Ti是根的子树。
树的表示法
1. 分支图表示法
2. 嵌套集合表示法
3. 广义表表示法
树的基本术语
树的结点:包含一个DE和指向其子树的所有分支;
结点的度:一个结点拥有的子树的个数,度为零的结点称为叶节点
树的度:树中所有结点的度的最大值Max(D(I)),含义:树中最大分支数为树的度
结点的层次及树的深度:根为第一层,根的孩子为第二层,若某结点为第k层,则其孩子为k+1层,树中结点的最大层次称为树的深度或高度
森林:m(m≥0)棵互不相交的树的集合。森林与树的概念相近,相互很容易转换
二、 树的基本运算
INITIATE(T):初始化操作,创建一棵空树
ROOT(T):求根函数,求树T的根
ROOT(x):求结点x所在树的根
PARENT(T,x):求双亲函数
CHILD(T,x,i):求x的第i个孩子
LSIBLING(T,x):求左兄弟函数
RSIBLING(T,x) :求右兄弟函数
CRT-TREE(x,F):建树函数,建立以结点x为根,森林F为子树的树
INS-CHILD(y,i,x):插入子树操作,将x作为y的第i棵子树
DEL-CHILD(x,i):删除子树操作,删除x的第i棵子树
TRAVERSE(T):遍历树操作
CLEAR(T):置空树操作
三、 二叉树
概念
二叉树是结点数为0或每个结点最多只有左右两棵子树的树。二叉树是一种特殊的树
特点:
(1) 每个结点最多只有两棵子树
(2) 子树有左右之分,不能颠倒
性质
性质1 在二叉树的第i层上至多有2i-1个结点(i≥1)。
性质2 深度为k的二叉树至多有2k-1个结点。
性质3 二叉树中,终端结点数n0与度为2的结点n2有如下关系:n0=n2+1
满二叉树:
深度为k,且有2k-1个结点的二叉树
特点:
(1) 每一层上结点数都达到最大
(2) 度为1的结点n1=0
结点层序编号方法:从根结点起从上到下(层内从左到右)逐层编号
完全二叉树:
深度为k,结点数为n的二叉树,当且仅当每个结点的编号都与相同深度的满二叉树从1到n的结点一一对应
特点:
(1) 每个结点i的左子树的深度Lhi-其结点i的右子树的深度Rhi等于0或1,即叶结点只可能出现在层次最大或者次大的两层上。
(2) 完全二叉树结点数n满足2k-1-1<n≤2k-1
(3) 满二叉树一定是完全二叉树,反之不成立。
性质:
性质4 结点数为n的完全二叉树,其深度为[log2n]+1
性质5 在按层序编号的n个结点的完全二叉树中,任意一结点i(1≤i≤n)有:
(1) i=1时,结点i是树的根;否则(i>1),结点i的双亲为结点[i/2]
(2) 2i>n时,结点i无左孩子,为叶结点;否则,结点i的左孩子为结点2i
(3) 2i+1>n时,结点i无右孩子,为叶结点;否则,结点i的右孩子为结点2i+1
二叉树的存储结构
1. 顺序存储结构
用一组地址连续的存储单元,以层序顺序存放二叉树的数据元素,结点的相对位置蕴含着结点之间的关系。
2. 链式存储结构
设计不同的结点结构,可以构成不同的链式存储结构。常用的有:
二叉链表
三叉链表
线索链表 用空链域存放指向前驱或后继的线索
性质6 含有n个结点的二叉链表中,有n+1个空链域。
四、 遍历二叉树和线索二叉树
1、 遍历二叉树
按一定的规律对二叉树的每个结点,访问且仅访问一次的处理过程。
访问:是一种抽象操作,是对结点的某种处理,例如可以是求结点的度,或层次、打印结点的信息,或做其他任何工作。
一次遍历后,使树中结点的非线性排列,按访问的先后顺序变为某种线性排列。
遍历的次序:设二叉树根为D,左子树为L,右子树为R,并限定先左后右,则有以下三种遍历次序:
LDR:中序遍历
中序遍历二叉树
算法思想:若二叉树非空,则:
1) 中序遍历左子树
2) 访问根节点
3) 中序遍历右子树
递归算法描述:
PROC inorder(bt:bitrepter):
{bt为BT根结点指针}
IF bt<>NIL THEN
[inorder(bt↑.lchild);
visit(bt↑.data);
inorder(bt↑.rchild);
]
ENDP;{inorder}
非递归算法描述
PROC inorder(bt:bitrepter):
{中序遍历非递归算法,s为存储二叉树结点指针栈}
INISTACK(s);
PUSH(s,bt);
WHILE NOT EMPTY(s) DO
[WHILE GETTOP(s)<>NIL DO
PUSH(s,GETTOP(s)↑.lchild); {最后压入的为空指针}
P:=POP(s); {弹掉空指针}
IF NOT EMPTY(s) THEN
[visit(GETTOP(s) ↑.data);{访问栈顶元素}
p:=POP(s); {将访问过的结点的指针弹出}
PUSH(s,p↑.rchild){将其右孩子压栈}]]
ENDP;{inorder}
LRD:后序遍历
后序遍历二叉树
算法思想:
若二叉树非空,则:
1) 后序遍历左子树
2) 后序遍历右子树
3) 访问根节点
算法描述:
PROC postorder(bt:bitrepter):
{bt为BT根结点指针}
IF bt<>NIL THEN
[postorder(bt↑.lchild);
postorder(bt↑.rchild);
visit(bt↑.data);
]
ENDP;{postorder}
DLR:先序遍历
先序遍历二叉树
算法思想:
若二叉树非空,则:
1) 访问根节点
2) 先序遍历左子树
3) 先序遍历右子树
算法描述:
PROC preorder(bt:bitrepter):
{bt为BT根结点指针}
IF bt<>NIL THEN
[
visit(bt↑.data);
preorder(bt↑.lchild);
preorder(bt↑.rchild);
]
ENDP;{preorder}
2、 线索二叉树
分析:n个结点有n-1个前驱和n-1个后继;一共有2n个链域,其中:n+1个空链域,n-1个指针域;
因此,必须用空链域来存放结点的前驱和后继。线索二叉树就是利用n+1个空链域来存放结点的前驱和后继结点的信息。
(1)结点结构
若结点有左子树,则左链域lchild指示其左孩子(ltag=0);否则,;令左链域指示其前驱(ltag=1);
若结点有右子树,则右链域rchild指示其右孩子(rtag=0);否则,;令右链域指示其后继(rtag=1);
称这种结点结构为线索链表;
其中指示前驱和后继的链域称为线索;
加上线索的二叉树称为线索二叉树;
对二叉树以某种次序遍历使其变为线索二叉树的过程称为线索化。
按中序遍历得到的线索二叉树称为中序线索二叉树;
按先序遍历得到的线索二叉树称为先序线索二叉树;
按后序遍历得到的线索二叉树称为后序线索二叉树;
(1)整体结构
增设一个头结点,令其lchild指向二叉树的根结点,ltag=0,rtag=1;
并将该结点作为遍历访问的第一个结点的前驱和最后一个结点的后继;
最后用头指针指示该头结点。
遍历线索二叉树:
有了线索二叉树,就容易遍历二叉树了。只要
(1) 先找要遍历的第一个结点;
(2) 然后依次找结点的后继;
(3) 重复(2)直到其后继为头结点。
1) 遍历中序线索二叉树
(1) 中序线索二叉树中,找结点的后继算法
算法思想:
对任意结点p,
若rtag=1,则rchild指向该结点的后继;
若rtag=0,则rchild指向该结点的右孩子,此时,应从右孩子开始,沿左指针前进,直到找到没有左孩子的结点s(ltag=1),则s就是p的后继,即后继是中序遍历右子树时,访问的第一个结点;
中序线索二叉树中,找结点后继算法:
FUNC in_next(p,thrt:thlinktp):thlinktp;
{在以thrt为头指针的中序线索二叉树上,查找指针p所指结点的后继}
s:= p↑.rchild;
IF p↑.rtag=0 THEN
WHILE s.ltag=0 DO
s:= s↑.lchild
RETURN(s)
ENDIF;{in_next}
(2) 遍历中序线索二叉树算法
PROC thrt_inorder(thrt:thlinktp);
{遍历以thrt为头指针的中序线索二叉树}
p:=thrt↑.lchild;{根结点}
WHILE p↑.lchild<>thrtDO p:= p↑.lchild
{定位要遍历的第一个结点}
WHILE p<>thrt DO [visit(p↑.data);
p:=in_next(p,thrt)]
ENDP;{thrt_inorder}
2) 遍历后序线索二叉树
(1) 后续线索二叉树中,找结点的后继算法
算法思想:
对任意结点p,
若p为二叉树的根,则无后继;
若p是双亲的右孩子、或是独生左孩子,则后继为双亲;
若p是有兄弟的左孩子,则后继为双亲的右子树上按后序遍历是,访问的第一个结点(叶结点);
后续线索二叉树中,找结点的后继算法
FUNC post_next(p,thrt:thlinktp):thlinktp;
{在以thrt为头指针的后序线索二叉树上,查找指针p所指结点的后继}
s:=parent(thrt,p); {在thrt上查找p的双亲}
IF s=thrt THEN RETURN (thrt);
IF s.rchild=p OR s.rtag=1
THEN RETURN (s);
WHILE s.rtag=0 DO
[s:=s.rchild;
WHILE s.ltag=0 DO s:=s.lchild;]
THEN RETURN (s);
ENDIF;{ post_next }
(2)遍历中序线索二叉树算法
PROC thrt_postorder(thrt:thlinktp);
{遍历以thrt为头指针的后序线索二叉树}
IF thrt<>thrt.lchild THEN {判断树是否为空}
[p:=thrt↑.lchild;search:=true; {p指向根结点}
WHILE search DO
[WHILE p↑.ltag=0 DO p:= p↑.lchild;
IF p↑.rtag=0 THEN p:= p↑.rchild
ELSE search:=false;]
WHILE p<>thrt DO [visit(p↑.data);
p:=post_next(p,thrt)]
ENDP;{thrt_postorder}
3) 遍历先序线索二叉树
(1)先序线索二叉树中,找结点的后继算法
算法思想:
对任意结点p:
若p有左孩子,则后继为左孩子;
若p无左孩子,有右孩子,则后继为右孩子
若p无左孩子,也无右孩子,则后继为右线索;
先序线索二叉树中,找结点后继算法
FUNC pre_next(p,thrt:thlinktp):thlinktp;
{在以thrt为头指针的先序线索二叉树上,查找指针p所指结点的后继}
IF p.ltag=0 THENRETURN (p↑.lchild);
ELSE RETURN (p↑.rchild)
ENDIF;{pre_next }
(2) 遍历先序线索二叉树算法
PROC thrt_preorder(thrt:thlinktp);
{遍历以thrt为头指针的先序线索二叉树}
p:=thrt↑.lchild;
WHILE p<>thrt DO [visit(p↑.data);
p:=pre_next(p,thrt)]
ENDP;{thrt_postorder}
五、树和森林
1. 双亲表示法
用一组地址连续的存储单元来存放树的结点,每个结点有两个域;
Data域——存放结点的信息;
Parent域——存放该结点双亲结点的位置
特点:求结点的双亲很容易,但求结点的孩子需要遍历整个向量。
2. 孩子表示法
每个结点的孩子用单链表存储,称为孩子链表。
n个结点可以有n个孩子链表(叶结点的孩子链表为空表)。
n个孩子链表的头指针用一个向量表示
3. 双亲孩子表示法
在孩子表示法的头指针向量中,增加一项,来表示该结点的双亲。
4. 孩子兄弟表示法
用二叉链表作为树的存储结构,每个结点的左链域指向该结点的第一个孩子,右链域指向下一个兄弟结点。
森林、树和二叉树的关系
1.树与二叉树的对应关系
树变为二叉树的方法
(1) 在兄弟之间加一连线;
(2) 对每一结点,只保留它与第一个孩子的连线,去掉与其他孩子的连线;
(3) 以树根为轴心,将整棵树顺时针旋转45度
特点:根无右子树
2.森林与二叉树的对应关系
(1)将森林F={T1,T2, …Tm}的各树分别转成二叉树BT1,BT2…BTm
将BTi+1作为BTi根结点的右子树(i=1,2,…,m-1),得到一棵BT。
哈夫曼树(Huffman)树是一类带权路径长度最短的树
一、哈夫曼树(Huffman)树(最有二叉树)
1.概念
两结点间的路径:从一结点到另一结点所经过的结点序列
路径长度:路径上的分支数
树的路径长度:从根到每一结点的路径长度之和
完全二叉树是路径长度最短的二叉树
考虑带权时:设树中有m个叶结点,每个叶结点带一个权值wi,且根到叶结点i的路径长度为Li(i=1,2,…,m),则树的带权路径长度为树中所有叶结点的权值与路径长度的乘积的总和。
即:WPL=WkLk
使WPL最小的二叉树为最优二叉树(Huffman树)
(1)完全二叉树并不一定是Huffman树
(2)HT是权值越大的越靠近根结点;
(3)HT不唯一,但WPL一定相等。
2.构造Huffman树算法
(1)以权值分别为W1,W2,……,Wn的n个结点,构成n棵二叉树T1,T2,……Tn并组成森林F={T1,T2,……Tn},其中每棵二叉树Ti仅有一个权值为Wi的根结点;
(2)在F中选取两棵根结点权值最小的树作为左右子树构造一棵新二叉树,并且置新二叉树根结点权值为左右子树上根结点权值之和(根据结点的权值=左右孩子权值之和,叶结点的权值=Wi);
(3)从F中删除这两棵二叉树,同时将新二叉树加入到F中;
(4)重复(2)(3)直到F中只含一棵二叉树为止,这棵二叉树就是Huffman树。
HT不唯一性,可能出现在:
(1)构造新树时,左右孩子未作规定。
(2)当有多个权值相同的树,可作候选树时,选择谁未作规定。
二、哈夫曼编码
1.问题的提出:
编码(文字->电文)通讯中常需要将文字转换成二进制进行传送
译码(电文->文字)收到电文后将电文转换成文字