哈夫曼树与哈夫曼编码
哈夫曼树
叶子结点的权值:对叶子结点赋予的一个有意义的数值量。
二叉树的带权路径长度:设二叉树具有 n 个带权值的叶子结点,从根结点到各个叶子结点的路径长度与相应叶子结点权值的乘积之和。记为:
哈夫曼树:给定一组具有确定权值的叶子结点,带权路径长度最小的二叉树。
哈夫曼树的特点:
- 权值越大的叶子结点,越靠近根结点,权值越小的叶子结点越远离根结点。
- 只有度为 0 (叶子结点)和度为 2 (分支节点)的结点,不存在度为 1 的结点
哈夫曼算法的基本思想:
哈夫曼树的构造过程:
W = {2, 4,5, 3}
-
初始化:先创建四个子树,每个子树都只有根结点(无双亲,无左右孩子)
-
选取与合并:选取上面四棵树中权值最小的两棵树( 2 和 3 )作为左右叶子结点,合并为新的二叉树,新二叉树的根结点是这两个叶子结点权值之和
-
删除与加入:
-
重复第二步和第三步
哈夫曼算法的存储结构
- 设置一个数组
huffTree[2n-1]
保存哈夫曼树中各点的信息,数组元素的结点结构。
strutc element
{
int weight;
int lchild, rchild, parent;
};
其中:
- weight:权值域,保存该结点的权值;
- lchild:指针域,结点的左孩子结点在数组中的下标;
- rchild:指针域,结点的右孩子结点在数组中的下标;
- parent:指针域,该结点的双亲结点在数组中的下标。
伪代码:
- 数组 huffTree 初始化,所有元素结点的双亲、左右孩子都置为 -1;
- 数组 huffTree 的前 n 个元素的权值置给定值 w[n] ;
- 进行n-1次合并
3.1 在二叉树集合中选取两个权值最小的根结点,其下标分别为i1, i2;
3.2 将二叉树i1,i2,合并为一棵新的二叉树 k;
存储流程:
-
初始化,所有元素结点的双亲、左右孩子都置为 -1(因为当前的四个结点都是根结点,无双亲,无左右子树)
-
将这四个权值填入到 weight 字段,这些权值是放在一个专门的 W 数组中,用 W 数组初始化 weight 这个字段
- 进入哈夫曼树的构建过程,此时需要在权值数组中找到 parent 为 -1,并且权值最小的两个结点,将这两个结点的下标(0 存入 i1,3 存入 i2),
- 初始化变量 k,k 保存了即将创建的新的子树的下标(也就是 4),然后将权值最小的两个结点合并,构成一个新的二叉树
构建新的二叉树,只需要在 huffTree 数组中,进行状态的更改即可。
- 首先将 k 所在单元的权值改为 i1 和 i2 所在单元的权值和,(2+3 = 5)
- 接着修改 2 和 3 的双亲结点字段,2 和 3 的双亲是 5,5所对应的下标是4,故将 2 和 3 的双亲字段改为 4。
- 然后修改 结点 5 的左右子树
-
然后继续循环,创建新的子树,刚才权值为 2 和权值为 3 的结点已经从集合中删除,重新循环,在剩下的三个权值中选取最小的两个结点,在编程时,就是在 parent 为 -1 中,选取权值最小的那两个(在这里有两个相同的权值,但是在扫描时,是先选中前面的),所以 i1 =1 ,i2 = 2,k 就指向即将构建树的结点的位置
-
然后需要构建新的二叉树,修改 huffTree 数组
-
然后进入新的循环,
伪代码:
哈夫曼编码
哈夫曼编码介绍
哈夫曼的应用—哈夫曼编码
编码: 给每一个对象标记一个二进制位串来表示一组对象。例:ASCII,指令系统
编码分类:
- 等长编码:表示一组对象的二进制位串的长度相等
- 不等长编码:表示一组对象的二进制位串的长度不相等
等长编码什么情况下空间效率最高?
- 如果能够让等长编码的长度最短,则占据的存储空间比较小,空间效率最高
不等长编码什么情况下空间效率最高?
- 出现频率比较高的对象采用比较短的编码,出现频率比较少的对象采用比较长的编码。这样的话存储空间占用较少,空间效率最高
不等长编码如何保证解码的唯一性?
前缀编码:一组编码中任一编码都不是其他任何一个编码的前缀。
- 前缀编码保证了在编码时不会有多种可能
- 出现概率越高越靠近根结点,可以得到最小的带权路劲长度,这种编码方式最节省空间
- 画出哈夫曼树:(不唯一,只要根结点是45就是对的)
- 将哈夫曼树所有的左子树标记为 0,右子树标记为 1(也可以左子树标 1,右子树标 0,哈夫曼编码和哈夫曼树不是唯一的,只要遵循一个统一的规定,就可以构建出哈夫曼编码),然后将所有的权值与字母对应起来
- 相应的编码就是从根结点到相应位置的路径
哈夫曼编码实现
哈夫曼编码方案:从叶子到根逆向求每个字符的哈夫曼编码