【数据结构】线索二叉树&哈夫曼树&森林

树和二叉树

线索二叉树

定义

我们遍历二叉树的得到一个线性序列,指向该线性序列的“前驱”和“后继”的指针,称为“线索”。

线索链表是在二叉链表的基础上增加两个标志域,如果该结点的左子树不空,那么Lchild域的指针指向其左子树,并且将左标志域的值设为“指针Link”,说明Lchild域中是指向左子树;否则,Lchild域的指针指向遍历得到的线性序列的“前驱”,将左标志域的值定为“线索Thread”。Rchild同样,但是在没有右子树时,指向“后继”

这种记录方式可以使得我们很快找到一个结点的“前驱”和“后继”

typedef enum{Link,Thread} PointerThr;
//Link == 0:指针,Thread == 1:线索
typedef struct BiThrNod{
	TElemType data;
	struct BiThrNode *lchild,*rchild; //左右指针
	PointerThr Ltag,Rtag;//左右标志
}BiThrNode,*BiThrTree;

遍历

先序遍历:
如果结点因为有右孩子导致无法找到其后继结点,如果结点有左孩子,则后继结点是其左孩子;否则,就一定是右孩子。

中序遍历:
如果一个结点有左子树,那么其前驱就是左子树的最右结点。 如果没有左子树,通过线索指明前驱。
如果一个结点有右子树,那么其后继就是右子树的最左结点。 如果没有右子树,通过线索指明后继。

void InOrderTraverse_Thr(BiThrTree T){
	p = T-> lchild; //p指向根结点
	while(p!=T){
		while(p->Ltag==Link) p = p->lchild;//如果左指针域指向左子树,则中序遍历的第一个结点为整个树最左下结点
		printf(p->data);
		while(p->Rtag==Thread && p->rchild!=T){
			p = p->rchild;//当没有右子树
			printf(p->data);
		}
		p = p->rchild;//按照中序遍历的规律,找其右子树中最左下的结点,也就是继续循环遍历
	}
}

线索化

通过在中序遍历的过程中,保存“前驱”和“后继”信息,进行中序线索化
就是在遍历二叉树的过程中,将二叉链表中的空指针改为指向直接前趋或者直接后继的线索。
在遍历过程中,如果当前结点没有左孩子,需要将该结点的 lchild 指针指向遍历过程中的前一个结点,所以在遍历过程中,设置一个指针(名为 pre ),时刻指向当前访问结点的前一个结点。

BiThrTree pre=NULL;
void InThreading(BiThrTree p){
    //如果当前结点存在
    if (p) {
        InThreading(p->lchild);//递归当前结点的左子树,进行线索化
        //如果当前结点没有左孩子,左标志位设为1,左指针域指向上一结点 pre
        if (!p->lchild) {
            p->Ltag=Thread;
            p->lchild=pre;
        }
        //如果 pre 没有右孩子,右标志位设为 1,右指针域指向当前结点。
        if (pre&&!pre->rchild) {
            pre->Rtag=Thread;
            pre->rchild=p;
        }
        pre=p;//pre指向当前结点
        InThreading(p->rchild);//递归右子树进行线索化
    }
}

树和森林的表示方法

树的三种存储结构

  1. 双亲表示法
#define MAX_TREE_SIZE 100
typedef struct PTNode{
	Elem data;//结点的值
	int parent;//双亲所在的位置
}PTNode;
typedef struct{
	PTNode nodes[MAX_TREE_SIZE];
	int r,n;//根结点的位置和结点总数
}PTree;
  1. 孩子链表表示法
    在每一个结点的后面使用链表结构,添加孩子结点的位置
#define MAX_SIZE 20
#define TElemType char
//孩子表示法
typedef struct CTNode {
    int child;//链表中每个结点存储的不是数据本身,而是数据在数组中存储的位置下标
    struct CTNode * next;
}ChildPtr;
typedef struct {
    TElemType data;//结点的数据类型
    ChildPtr* firstchild;//孩子链表的头指针
}CTBox;
typedef struct {
    CTBox nodes[MAX_SIZE];//存储结点的数组
    int n, r;//结点数量和树根的位置
}CTree;
  1. 孩子-兄弟表示法
    每个结点包含三个值:
    节点的值;指向孩子节点的指针;指向兄弟节点的指针;
#define ElemType char
typedef struct CSNode{
    ElemType data;
    struct CSNode * firstchild,*nextsibling;
}CSNode,*CSTree;

通过孩子兄弟表示法,任意一棵普通树都可以相应转化为一棵二叉树,换句话说,任意一棵普通树都有唯一的一棵二叉树于其对应。

森林与二叉树&&树与二叉树

转化

在这一部分主要考察将森林转化为二叉树和将树转为二叉树,在这个转化过程中,我们重新定义了左右子树,将左子树定为孩子,右子树定为兄弟,这个孩子兄弟表示法相同

  1. 树转换为二叉树
    将所有处于同一层的相邻兄弟结点之间加一条连线;对于每个结点只保留从左数第一个孩子与该结点之间的连线,其他孩子结点与该结点之间的连线全部去除;将转换后的树进行一定旋转,让它变好看一些
  2. 森林转换为二叉树
    由于森林中有很多棵树,我们先将每棵树都转换为二叉树;而后第一棵树不动,依次将后一棵树的根结点作为前一棵树根结点的右子树连在一起,直到连接完森林中所有的树

遍历

  1. 树的遍历只有先根和后根,没有中序遍历
  2. 森林的遍历只有先序和中序遍历,没有后序遍历
    先序遍历时,先访问森林中的第一棵树的根结点,然后先序访问第一棵树的子树,最后依次从左到右对森林中每一棵树进行先根遍历;
    中序遍历时,先后根遍历第一棵树的子树,然后访问第一棵树的根结点,最后依次从左到右对森林中每一棵树进行后根遍历

哈夫曼树与哈夫曼编码

带权路径长度:树中所有叶子结点的带权路径长度之和,也就是所有叶子结点上的权值乘以它所在层次(根为0)的和

哈夫曼树是一棵带权路径长度取最小值的m叉树,也就是“最优树”

构造哈夫曼树

从给定的权值中,挑选m个最小值,构成一棵m叉树,计算m个最小值的总和,然后从权值序列中,删除选定的最小值,将计算后的总和加入(及构造出来的m叉树)。重复此过程,直到只剩一个权值,即为构造的哈夫曼树

Huffman树只有度为0和度为2的结点

哈夫曼编码

哈夫曼编码就是在哈夫曼树的基础中,将左子树变为0,右子树编为0,(也可以颠倒)对叶子结点进行编码

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值