数据结构【第九天】:树(三)
线索二叉树
由于二叉树的每个结点均有两个指针域,一个指向左孩子,一个指向右孩子。但是叶子并不存在左右孩子,会有许多的空指针浪费空间。于是我们将利用这些空地址,存放指向结点在某种遍历次序下的前序和后继的结点地址。
我们将这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树。
对二叉树以某种次序遍历使其变为线索二叉树的过程称为线索化
通常,按照某种遍历方式,我们将空指针域中的lchild指向前驱,rchild指向后继。但是为了区分lchild指向的是前驱还是左孩子,需要引入一个标志位ltag和rtag,结点的结构如下:
lchild | ltag | data | rtag | rchild |
1.当ltag为0时表示lchild指向结点的左孩子,为1表示指向前驱;
2.当rtag为0时表示rchild指向结点的右孩子,为1表示指向后驱;
结构定义
trpedef enum{Link,Thread} PointerTag;
typedef struct BiThrNode
{
TElemType data;
struct BiThrNode *lchild,*rchild;
PointerTag LTag;
PointerTag RTag;
} BiThrNode, *BiThrTree;
线索化的过程就是在遍历的过程中修改空指针的过程
中序遍历线索化的递归函数代码
BiThrTree pre; //设置全局变量,始终指向刚访问过的结点
void InThreading(BiThread p)
{
if(p)
{
InThreading(p->lchild); //递归左子树线索化
if(!p->lchild) //结点没有左孩子
{
p->LTag = Thread; //前驱标志位指向1
p->lchile = pre; //左孩子指向前驱
}
if(!pre->rchild) //前驱没有右孩子
{
pre->RTag = Thread; //后继标志位指向1
pre->rchild = p; //前驱的右指针指向后继即当前结点p
}
pre = p; //保证pre始终为刚访问过的结点
InThreading(p->rchild); //递归右子树线索化
}
}
中序遍历代码
#include <iostream>
//T为头节点,其左链lchild指向根节点,其右链rchild指向中序遍历的最后一个结点
//所以中序遍历的二叉线索树链表表示为T
Status InOrderTraverse_Thr(BiThrTree T)
{
BiThrTree p;
p = T->lchild; //p指向根节点
while(p!=T) //空树或者遍历结束,p == T
{
while(p->LTag == Link) //如果结点有左孩子
p = p->lchild; //直到最左的叶子,即中序遍历的第一个结点
std::cout<<p->data; //打印数据
while(p->RTag==Thread && p->rchild != T) //当右指针表示后继且不为空时
{
p = p -> rchild; //遍历后继结点
std::cout<<p->data; //打印数据
}
p = p->rchild; //指向节点右孩子
}
return ok;
}
当所用的二叉树需要经常遍历或查找结点时需要某种遍历序列中的前驱和后继,那么可采用线索二叉链表的存储结构。
树、森林与二叉树转换
树转化为二叉树
1.加线。在所有兄弟结点之间加一条连线
2.去线。对树中的每个结点,只保留它与第一个孩子结点的连线,删除它与其他孩子结点之间的连线
3.层次调整。以树根结点为轴心,将整棵树顺时针旋转一定角度,使☞结构层次分明。注意:第一个孩子是二叉树结点的左孩子,而其他兄弟转换过来成为第一个孩子的右孩子
森林转化为二叉树
1.把每棵树转换成二叉树
2.第一颗树不动,从第二棵开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来。当所有的二叉树连接起来后就得到了由森林转换而来的二叉树
二叉树转化为树
1.加线。若某结点的 左孩子存在,则将这个左孩子的所有的右结点都作为此结点的孩子。将该结点与这些右孩子结点用线连接起来
2.去线。删除原二叉树中所有的结点与其右孩子结点的连线
3.层次调整。
二叉树转化为森林
1.从根结点开始,若右孩子存在,则把与右孩子结点的连线删除,再查看分离后的二叉树,若右孩子存在,则连线删除......,直到所有右孩子连线都删除为止,得分离的二叉树。
2.再将没棵分离后的二叉树转换为树即可。
树与森林的遍历
当以二叉链表作为树的存储结构时,树的先根遍历和后根遍历完全可以借用二叉树的前序遍历和中序遍历的算法来实现
赫夫曼树及其应用
基本概念
从树中的一个结点到另一个结点之间的分支构成两个结点之间的路径,路径山的分支书怒称作路径长度。
树的路径长度就是从树根到每一个结点的路径长度之和
结点的带权路径为路径长度与结点上权的乘积
树的带权路径长度就是树中所有叶子结点的带权路径长度之和
其中带权路径长度WPL最小的二叉树称作赫夫曼树
构造方法
1.根据给定的n个权值{w1,w2,...,wn}构成n棵二叉树的集合F={T1,T2,...,Tn},其中每棵二叉树Ti中只有一个带权为wi根结点,其左右子树均为空;
2.在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根节点的权值为其左右子树上根结点的权值之和
3.F中删除这两棵树,同时将新得到的二叉树加入到F中
4.重复2、3直到F只含一棵树为止,可得赫夫曼树
例子
A5,E10,B15,D30,C40 字母为结点名,数字为权值
构造步骤图如下
应用:赫夫曼编码
传统编码每个字母需要3位二进制表示,而赫夫曼编码,根据字母的出现频率,可以给定权值,然后构造赫夫曼树,叶子结点即为字母,同时将左分支的权值改为0,右分支的权值改为1,则可以获得新的编码方式
如上图
A的编码位01
B的编码为1001
.这种方式随着字符的增多和权重的不同会显现更多优势,但是注意在使用这种编码方式时,发送方和接收方需要约定号同样的赫夫曼编码规则!