第六章、树和二叉树
6.4树和森林
6.4.1树的存储结构
(一)双亲表示法--找双亲容易,找孩子麻烦
连续空间存放,每个节点增设单元,指向双亲。
- 最大结点数
- 结点:数据+双亲
- 树:结点数组+结点数
# define MAX_TREE_SIZE 100
![d6d956b8a0539b242f81bd8fe6d1d0cc.png](https://i-blog.csdnimg.cn/blog_migrate/adb576c92a59b4ef285f9dcf8b737a4e.jpeg)
(二)孩子表示法
(1)设置多个指针域,指向每个孩子
![8a1b7b2fdd35b373e1a83c5c56ec0e91.png](https://i-blog.csdnimg.cn/blog_migrate/b9828d62f840fed7327a831f27390473.png)
问题:每个结点的指针域个数都是树的度(结点的最大后继个数),但对于大多数结点,它的后继数达不到树的度,造成空间浪费。
考虑添加结点的度信息
![d622f2ba090ac37029da317903af9768.png](https://i-blog.csdnimg.cn/blog_migrate/4e89c9e00e42f3881e4a3235f0d9cf36.png)
问题:空间虽然节省,但操作不方便!
(2)顺序存储结点,链式存储孩子(类似图的邻接表)
※用数组来对结点进行编号,用链表存储孩子
//孩子链表的结点:数据域+指针域
![a748857d311ee2f5bd5adcbbc3aa488f.png](https://i-blog.csdnimg.cn/blog_migrate/cc90aa3a5488048ef2296b55a63c1e6e.jpeg)
问题:找孩子方便,但找双亲不方便,所以考虑基于这种表示,带上双亲结点的信息
![bc23ff48d73cdc51488f56f2af5395dc.png](https://i-blog.csdnimg.cn/blog_migrate/338a4db3acf969e38a0db02fcd18e1f8.jpeg)
(3)孩子兄弟表示法(二叉树表示法)
将树转化为二叉树,用二叉链表来表示:
二叉链表:数据域+第一个孩子指针+下一个兄弟指针
typedef
访问第
访问第
6.4.2森林与二叉树转换
思路:把森林中不相交的数看做兄弟,使用孩子兄弟表示法
树和森林转换为二叉树:
兄弟用线连起来--双亲到孩子的连线只保留第一个
二叉树还原为树或森林:
如果结点是左孩子,则双亲连接该结点的右孩子,右孩子的右孩子...,然后删除所有双亲到右孩子的连线
![43ecd0499af8831c9fa3fd1d52bb1177.png](https://i-blog.csdnimg.cn/blog_migrate/4064b6e6d7e621eab81b33013874ce5a.png)
ps:知道原理也可以转换,不一定按上述文字规则。
6.4.3树和森林的遍历
(一)树的遍历
- 先根遍历:访问根--依次先根遍历每棵子树
- 后根遍历:依次后根遍历每棵子树--访问根
ps:先根遍历就是对应二叉树的先序遍历;后根遍历就是对应二叉树的中序遍历
(二)森林的遍历
先序遍历:访问第一棵树的根--先序遍历第一棵树根的子树森林--先序遍历其他树构成的森林
中序遍历:中序遍历第一棵树根的子树森林--访问第一棵树的根--中序遍历其他树构成的森林
6.5哈弗曼树及其应用
6.5.1基本概念
- 路径:序列
,前一个是后一个的双亲,则构成
的路径
- 路径长度:路径结点-1
- **树的路径长度:树根到每一结点的路径长度之和
- 结点带权路径长度:根结点到该结点的路径长度 * 结点权重
- 树的带权路径长度:叶子结点(!!)的带权路径长度之和
![319501b5e5091f966aa1183abf31664a.png](https://i-blog.csdnimg.cn/blog_migrate/5551aaeca504a443cc3c6e784848bf38.jpeg)
6.5.2哈夫曼树(最优二叉树)
定义:给定叶子结点为n个及相应权,对应的所有二叉树中,带权路径长度最小的!如上图中的C
6.5.3构造哈夫曼树--大权离根近
- 在叶子结点中选取权值最小的两个作为左右子树(一般左<右,视题目而定)配上一个根结点构成新的二叉树,根结点的权值为两个叶子结点的权值之和
- 把新二叉树的根作为叶子结点加入,再选取最小的叶子结点(此时可能1个可能2个)构建新的二叉树
- 重复上述两步直到所有叶子结点都选中
![25371a57fd9b1eb819fcb3aa2291de77.png](https://i-blog.csdnimg.cn/blog_migrate/b9e9975a8895faff4245d0ce4b87929b.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](https://i-blog.csdnimg.cn/blog_migrate/23351d66e0a6a29307bb8be502cc8d5a.png)
得到对应的编码为A--0,B--110,C-10,D-111,总编码长度为13.
注意:根据上面方法构造出来的哈夫曼树不是唯一的,所以我们一般假定左子树的根结点权重小于右子树的根结点权重。
6.5.5算法实现哈夫曼编码
因为哈夫曼树每个结点的度只能为0或2,利用二叉树的性质4(叶子结点数
思路:因为编码是叶子到根,译码是根到叶子,所以我们需要知道双亲和孩子的信息.
- HT为结构体数组,每个数组单元包含结点的权重、双亲、左右孩子信息。
- HC是指针数组,用于编码,对应于
个叶子结点,
![9438c06bb8647d1dbe00c6d351078429.png](https://i-blog.csdnimg.cn/blog_migrate/7a21f79d17b038562e7f9edcd12c442d.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中
//用结构体数组表示哈夫曼树,每个单元为权重+双亲+左右孩子