已知p是一个指向类a的数据成员m的指针_数据结构第六讲(树和二叉树:树与森林的存储与遍历,树和森林与二叉树的转换,哈夫曼树)...

第六章、树和二叉树

6.4树和森林

6.4.1树的存储结构

(一)双亲表示法--找双亲容易,找孩子麻烦

连续空间存放,每个节点增设单元,指向双亲。

  • 最大结点数
  • 结点:数据+双亲
  • :结点数组+结点数
# define MAX_TREE_SIZE  100

d6d956b8a0539b242f81bd8fe6d1d0cc.png

(二)孩子表示法

(1)设置多个指针域,指向每个孩子

8a1b7b2fdd35b373e1a83c5c56ec0e91.png

问题:每个结点的指针域个数都是树的度(结点的最大后继个数),但对于大多数结点,它的后继数达不到树的度,造成空间浪费。

考虑添加结点的度信息

d622f2ba090ac37029da317903af9768.png

问题:空间虽然节省,但操作不方便!

(2)顺序存储结点,链式存储孩子(类似图的邻接表)

※用数组来对结点进行编号,用链表存储孩子

//孩子链表的结点:数据域+指针域

a748857d311ee2f5bd5adcbbc3aa488f.png

问题:找孩子方便,但找双亲不方便,所以考虑基于这种表示,带上双亲结点的信息

bc23ff48d73cdc51488f56f2af5395dc.png

(3)孩子兄弟表示法(二叉树表示法)

将树转化为二叉树,用二叉链表来表示:

二叉链表:数据域+第一个孩子指针+下一个兄弟指针

typedef   

访问第

个孩子:firstchild走一步+nextsibling走

访问第

个兄弟:nextsibling走

6.4.2森林与二叉树转换

思路:把森林中不相交的数看做兄弟,使用孩子兄弟表示法

树和森林转换为二叉树

兄弟用线连起来--双亲到孩子的连线只保留第一个

二叉树还原为树或森林:

如果结点是左孩子,则双亲连接该结点的右孩子,右孩子的右孩子...,然后删除所有双亲到右孩子的连线

43ecd0499af8831c9fa3fd1d52bb1177.png

ps:知道原理也可以转换,不一定按上述文字规则。

6.4.3树和森林的遍历

(一)树的遍历

  • 先根遍历:访问根--依次先根遍历每棵子树
  • 后根遍历:依次后根遍历每棵子树--访问根

ps:先根遍历就是对应二叉树的先序遍历;后根遍历就是对应二叉树的中序遍历

(二)森林的遍历

先序遍历:访问第一棵树的根--先序遍历第一棵树根的子树森林--先序遍历其他树构成的森林

中序遍历:中序遍历第一棵树根的子树森林--访问第一棵树的根--中序遍历其他树构成的森林

6.5哈弗曼树及其应用

6.5.1基本概念

  • 路径:序列
    ,前一个是后一个的双亲,则构成
    的路径
  • 路径长度:路径结点-1
  • **树的路径长度:树根到每一结点的路径长度之和
  • 结点带权路径长度:根结点到该结点的路径长度 * 结点权重
  • 树的带权路径长度叶子结点(!!)的带权路径长度之和

319501b5e5091f966aa1183abf31664a.png
带权路径长度的例子

6.5.2哈夫曼树(最优二叉树)

定义:给定叶子结点为n个及相应权,对应的所有二叉树中,带权路径长度最小的!如上图中的C

6.5.3构造哈夫曼树--大权离根近

  • 在叶子结点中选取权值最小的两个作为左右子树(一般左<右,视题目而定)配上一个根结点构成新的二叉树,根结点的权值为两个叶子结点的权值之和
  • 把新二叉树的根作为叶子结点加入,再选取最小的叶子结点(此时可能1个可能2个)构建新的二叉树
  • 重复上述两步直到所有叶子结点都选中

25371a57fd9b1eb819fcb3aa2291de77.png

6.5.4哈夫曼编码

问题:想要发送电文为ABACCDA,但只能用0-1来表示

最简单的编码方式为等长编码,例如A--00,B--01,C--10,D-11,对应的电文编码为00010010101100,总长度为14,现想缩短电文长度

初步想法:采用不等长编码,数量多的编码短!如A--0,B--00,C--1,D-01,这样对应的电文编码为000011010,总长度为9,长度确实缩短了!但是出现了严重的问题:000表示A还是AB?因此我们要求任意字符的编码都不是另一字符编码的前缀(前缀编码)

进一步想法:利用二叉树--字符作为叶子结点--根结点出发到叶子结点的路径就是编码的字符串(约定左0右1)--这样得到的一定是“前缀编码”,因为只有当一个结点是另一个结点的双亲时,它的编码才是另一个结点的前缀。编码总长度,就是对应二叉树的带权路径长度,叶子结点的权就是该字符在电文中出现的次数,自然考虑用哈夫曼树

对刚才的例子ABACCDA,数出各字符出现的次数:A--3,B--1,C--2,D--1,然后让其作为叶子结点,构造哈夫曼树如下

8f52b1416f5373cbe2c29d8db5dacebb.png

得到对应的编码为A--0,B--110,C-10,D-111,总编码长度为13.

注意:根据上面方法构造出来的哈夫曼树不是唯一的,所以我们一般假定左子树的根结点权重小于右子树的根结点权重。

6.5.5算法实现哈夫曼编码

因为哈夫曼树每个结点的度只能为0或2,利用二叉树的性质4(叶子结点数

=度为2的结点数+1),所以度为2的节点数为
,总结点数为

思路:因为编码是叶子到根,译码是根到叶子,所以我们需要知道双亲和孩子的信息.

  • HT为结构体数组,每个数组单元包含结点的权重、双亲、左右孩子信息。
  • HC是指针数组,用于编码,对应于
    个叶子结点,
    每个单元存放指向编码基地址的指针

9438c06bb8647d1dbe00c6d351078429.png
  • 用结构体数组表示哈夫曼树,每个单元为权重+双亲+左右孩子
  • 初始哈夫曼树:前n个数组单元存放叶子结点,其权重已知,双亲和左右孩子先设为0, 后n-1个结点的权重,双亲,左右孩子都设为0
  • 建哈夫曼树:选出当前权重最小的且双亲还是0的两个结点--更新HT数组单元(权重为两者权重之和,) 左右孩子分别为最小的和次小的结点--权重最小的两个结点的双亲置为新结点
  • 叶子到根逆向编码:为编码数组HC分配n+1个结点空间,其中0号单元不用--为编码工作空间cd分配 n个结点空间,因为路径长度最多n-1,结尾用来存放0--从cd的第n-1号单元开始往前走,判断结点是双亲的 左孩子还是右孩子并赋予0或1,遇到根节点(parent为0)则终止--将编码空间cd的结果拷贝HC中
//用结构体数组表示哈夫曼树,每个单元为权重+双亲+左右孩子
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值