赫夫曼树

来源:互联网 发布:php取对象的值 编辑:程序博客网 时间:2024/06/07 07:02

原文参考自:http://blog.csdn.net/shuangde800/article/details/7341289

及http://www.java3z.com/cwbwebhome/article/article8/83550.html?id=4624

一、哈夫曼树的概念和定义

 

什么是哈夫曼树?

让我们先举一个例子。

判定树:

        在很多问题的处理过程中,需要进行大量的条件判断,这些判断结构的设计直接影响着程序的执行效率。例如,编制一个程序,将百分制转换成五个等级输出。大家可能认为这个程序很简单,并且很快就可以用下列形式编写出来:
if(score<60)cout<<"Bad"<<endl;else if(score<70)cout<<"Pass"<<endlelse if(score<80)cout<<"General"<<endl;else if(score<90)cout<<"Good"<<endl;elsecout<<"Very good!"<<endl;
若考虑上述程序所耗费的时间,就会发现该程序的缺陷。在实际中,学生成绩在五个等级上的分布是不均匀的。当学生百分制成绩的录入量很大时,上述判定过程需要反复调用,此时程序的执行效率将成为一个严重问题。
 
但在实际应用中,往往各个分数段的分布并不是均匀的。下面就是在一次考试中某门课程的各分数段的分布情况: 


下面我们就利用哈夫曼树寻找一棵最佳判定树,即总的比较次数最少的判定树。

第一种构造方式:


第二种构造方式:


这两种方式,显然后者的判定过程的效率要比前者高。在也没有别地判定过程比第二种方式的效率更高。
 
我们称判定过程最优的二叉树为哈夫曼树,又称最优二叉树
 
 
===================================================================================================
 

定义哈夫曼树之前先说明几个与哈夫曼树有关的概念:

 

路径: 树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。

路径长度:路径上的分枝数目称作路径长度。

树的路径长度:从树根到每一个结点的路径长度之和。

结点的带权路径长度:在一棵树中,如果其结点上附带有一个权值,通常把该结点的路径长度与该结点上的权值

                                       之积称为该结点的带权路径长度(weighted path length)


什么是权值?( From 百度百科 )

     计算机领域中(数据结构)

  权值就是定义的路径上面的值。可以这样理解为节点间的距离。通常指字符对应的二进制编码出现的概率。

  至于霍夫曼树中的权值可以理解为:权值大表明出现概率大!

  一个结点的权值实际上就是这个结点子树在整个树中所占的比例.

  abcd四个叶子结点的权值为7,5,2,4. 这个7,5,2,4是根据实际情况得到的,比如说从一段文本中统计出abcd四个字母出现的次数分别为7,5,2,4. 说a结点的权值为7,意思是说a结点在系统中占有7这个份量.实际上也可以化为百分比来表示,但反而麻烦,实际上是一样的.

 

树的带权路径长度:如果树中每个叶子上都带有一个权值,则把树中所有叶子的带权路径长度之和称为树的带

                                   权路径长度。


======================================================================================================
 
一般来说,用n(n>0)个带权值的叶子来构造二叉树,限定二叉树中除了这n个叶子外只能出现度为2的结点。
 
那么符合这样条件的二叉树往往可构造出许多颗,
 
 
其中带权路径长度最小的二叉树就称为哈夫曼树最优二叉树
 
===============================================================================
 n 个叶子构成的哈夫曼树其带权路径长度唯一吗?确实唯一。树形唯一吗?不唯一

因为将森林中两棵权值最小和次小的子棵合并时,哪棵做左子树,哪棵做右子树并不严格限制。图之中的做法是把权值较小的当做左子树 , 权值较大的当做右子树。如果反过来也可以,画出的树形有所不同,但 WPL 值相同。为了便于讨论交流在此提倡权值较小的做左子树 , 权值较大的做右子。


二、 哈夫曼树的应用
(1) 用于最佳判断过程。(上面已提到)
(2) 用于通信编码

   在通信及数据传输中多采用二进制编码。为了使电文尽可能的缩短,可以对电文中每个字符出现的次数进行统计。设法让出现次数多的字符的二进制码短些,而让那些很少出现的字符的二进制码长一些。假设有一段电文,其中用到 4 个不同字符A,C,S,T,它们在电文中出现的次数分别为 8 , 2 , 4 , 5 。把 8 , 2 , 4 , 5 当做 4 个叶子的权值构造哈夫曼树如图所示。在树中令所有左分支取编码为 0 ,令所有右分支取编码为1。将从根结点起到某个叶子结点路径上的各左、右分支的编码顺序排列,就得这个叶子结点所代表的字符的二进制编码,如图所示。


这些编码拼成的电文不会混淆,因为每个字符的编码均不是其他编码的前缀,这种编码称做前缀编码。会混淆的不等长编码举例:

    可以为A,C,S,T四个字符分别分配0,00,1,01,并发送序列:000011010,其长度只有9个二进制位,但随之带来了一个问题,接收方接到这段电文后无法进行译码,因为无法断定前面4个0是4个A,1个B、2个A,还是2个B,即译码不唯一,因此这种编码方法不可使用。

  关于信息编码是一个复杂的问题,还应考虑其他一些因素。比如前缀编码每个编码的长度不相等,译码时较困难。还有检测、纠错问题都应考虑在内。这里仅对哈夫曼树举了一个应用实例。

#include<iostream>#include<cstdio>#include<cstring>using namespace std;#define N 10         // 带编码字符的个数,即树中叶结点的最大个数#define M (2*N-1)    // 树中总的结点数目class HTNode{        // 树中结点的结构public: unsigned int weight;unsigned int parent,lchild,rchild;};                    class HTCode{public:char data;      // 待编码的字符int weight;     // 字符的权值char code[N];   // 字符的编码};void Init(HTCode hc[], int *n){// 初始化,读入待编码字符的个数n,从键盘输入n个字符和n个权值int i;printf("input n = ");scanf("%d",&(*n));printf("\ninput %d character\n",*n); fflush(stdin);for(i=1; i<=*n; ++i)scanf("%c",&hc[i].data);printf("\ninput %d weight\n",*n);for(i=1; i<=*n; ++i)scanf("%d",&(hc[i].weight) );fflush(stdin);}//void Select(HTNode ht[], int k, int *s1, int *s2){// ht[1...k]中选择parent为0,并且weight最小的两个结点,其序号由指针变量s1,s2指示int i;for(i=1; i<=k && ht[i].parent != 0; ++i){ ; ;}*s1 = i;for(i=1; i<=k; ++i){if(ht[i].parent==0 && ht[i].weight<ht[*s1].weight)*s1 = i;}for(i=1; i<=k; ++i){if(ht[i].parent==0 && i!=*s1)break;}*s2 = i;for(i=1; i<=k; ++i){if(ht[i].parent==0 && i!=*s1 && ht[i].weight<ht[*s2].weight)*s2 = i;}}void HuffmanCoding(HTNode ht[],HTCode hc[],int n){// 构造Huffman树ht,并求出n个字符的编码char cd[N];int i,j,m,c,f,s1,s2,start;m = 2*n-1;for(i=1; i<=m; ++i){if(i <= n)ht[i].weight = hc[i].weight;elseht[i].parent = 0;ht[i].parent = ht[i].lchild = ht[i].rchild = 0;}for(i=n+1; i<=m; ++i){Select(ht, i-1, &s1, &s2);ht[s1].parent = i;ht[s2].parent = i;ht[i].lchild = s1;ht[i].rchild = s2;ht[i].weight = ht[s1].weight+ht[s2].weight;}cd[n-1] = '\0';for(i=1; i<=n; ++i){start = n-1;for(c=i,f=ht[i].parent; f; c=f,f=ht[f].parent){if(ht[f].lchild == c)cd[--start] = '0';elsecd[--start] = '1';}strcpy(hc[i].code, &cd[start]);}}int main(){int i,m,n,w[N+1];HTNode ht[M+1];HTCode hc[N+1];Init(hc, &n);     // 初始化 HuffmanCoding(ht,hc,n);   // 构造Huffman树,并形成字符的编码for(i=1; i<=n; ++i)  printf("\n%c---%s",hc[i].data,hc[i].code);  printf("\n");return 0;}

0 0
原创粉丝点击