哈夫曼树与哈夫曼编码

哈夫曼树在数据结构里可是鼎鼎大名啊,不过学数据结构已经是一年之前的事情了,没看书之前,还真想不起来哈夫曼树是个啥,现在就来说说,哈夫曼树到底是个什么东东。
一、基本概念
哈夫曼树:哈夫曼树就是带权路径长度最短的树,又称最优二叉树。
路径:一个结点到另一个结点之间的分支序列,构成这两个结点之间的路径。
路径长度:两个结点之间路径的条数
树的路径长度:从根结点到每个结点的路径长度之和   (注意 是所有的啊)
带权路径长度:结点到根结点间的路径的长度与结点的权的乘积
树的带权路径长度(WPL):树中所有叶子结点的带权路径长度之和。   (就是乘以权值)
最优二叉树:就是树的带权路径长度最短的二叉树。
二、哈夫曼树的建立

比如现在有序列  2,4,8,9,则构成的哈夫曼树如下:

哈夫曼树与哈夫曼编码_第1张图片

构建思想:
(1)2,4,8,9 中选出最小和次小 2,4 最为最低的叶子结点
(2)6,8,9 中选出最小和次小  6,8
(3)14,9 中选出最小和次小 14,9
这就是整个的建立过程。要根据这个思想,我们写成算法,这就是有名的哈夫曼算法。
三、哈夫曼算法
对于n个叶子的哈夫曼树,有 n-1个度为2 的叶子结点,所以哈夫曼树一共有2n-1个结点,那么就需要 2n-1个存储单元。
哈夫曼树的保存一般采用静态三叉链表来存储。静态三叉链表的结点结构如下:
                 weight(权值)     parent(父母结点在数组中的下标)    LChild(左孩子的下标)     RChild(右孩子的下标)
静态三叉链表数组中,前n个元素用来存储叶子结点,后 n-1个元素用来存储不断产生的新结点。为什么这样放,就要看看上数第三行的话咯。
接下来直接上算法:

void hfman(HuffmanTree ht,int w,int n)
{
m = 2*n-1;
for(i = 1;i <= n;i++)        //初始化前n个元素 ,注意下标是从1开始
{
	ht[i] = {w[i],0,0,0};      //参照静态三叉链表的结构
}
for(i = n+1;i <= m;i++)      //初始化后n-1个元素
{
	ht[i] = {0,0,0,0};  
}
for(i = n+1;i<= m;i++)
{
	select(ht,i-1,&s1,&s2);    //该方法实现  在ht的前 i -1 项中选出双亲为0且权值最小的两个结点 s1,s2
	ht[i].weight = ht[s1].weight + ht[s2].weight;     //改变相应的数据项
	ht[i].LChild = s1;
	ht[i].RChild = s2;
	ht[s1].parent = ht[s2].parent = i;
}
}


四、哈夫曼编码
上次写到这块,之间隔了一个礼拜多,真的是颓废了一个礼拜。算了,不说了,继续说哈夫曼编码
(1)用途:解决压缩问题,
(2)前缀编码:同一个字符集中任何一个字符的编码都不是另一个字符编码的前缀,比如说,0001 和 00011 这就不是前缀编码,开始我没有认真看这个定义,都理解反了,所以,还是看到这里,要是刚才没认真看的赶紧再看看。
(3)哈夫曼编码的基本思想:每一个字符对应一个叶子,字符出现的频率对应权值,权值大的叶子尽可能的距根近,其编码短,出现次数少的距根远,编码长。
(4)哈夫曼编码:在哈夫曼树中,左分支表示符号 0 ,右分支表示符号 1,用根到叶子结点路径上的分支符号组成的串作为叶子结点的编号。 (左0右1)
其实哈夫曼编码就是最优二进制前缀码,
为什么是最优呢,因为它是带权路径长度最短的树,就是WPL,如果没有想起来还是看看前面说的吧。
为什么是二进制,因为它是用0和1表示的左右子树。
为什么是前缀码,从根到每个叶子的路径是不同的,可能前半部分是相同的,但是到后面一定会分叉,所以一个不可能是另一个的前缀。
(5)哈夫曼编码算法的实现:
在主体上分为两个部分:构造哈夫曼树(这个前面说过了)  求每个叶子结点的哈夫曼编码,接下来就说一下求叶子结点的哈夫曼编码
A.从叶子结点开始,向上追溯,左分支得到 0,右分支得到 1
B.从叶子结点向上追溯是 哈夫曼编码的逆串,所以,先将该串存于数组cd中,从后向前放,(这里注意,所以cd数组最后一个要事先放入 '\0'),由start指针控制存放的次序.
C.到达根结点说明该叶子结点编码完成,然后将cd数组中start为开始的串复制到动态申请的编码串空间中。
这块的知识点就是这么些,可能总结的不全,如果有什么不足之处,请多多指点。


你可能感兴趣的:(数据结构,二叉树)