码迷,mamicode.com
首页 > 其他好文 > 详细

哈弗曼树的构建,哈夫曼编码、译码

时间:2016-05-07 06:28:40      阅读:284      评论:0      收藏:0      [点我收藏+]

标签:

哈夫曼树的基本概念
 
 哈夫曼树(Huffman Tree),又叫最优二叉树,指的是对于一组具有确定权值的叶子结点的具有最小带权路径长度的二叉树。
 
(1)路劲(Path):从树中的一个结点到另一个结点之间的分支构成两个结点间的路径。
(2)路径长度(Path Length):路径上的分支树。
(3)树的路径长度(Path Length of Tree):从树的根结点到每个结点的路径长度之和。在结点数目相同的二叉树中,完全二叉树的路径长度最短。
(4)结点的权(Weight of  Node):在一些应用中,赋予树中结点的一个有实际意义的树。
(5)结点的带权路径长度(Weight Path Length of Node):从该结点到树的根结点的路径长度与该结点的权的乘积。
(6)树的带权路径长度(WPL):树中所有叶子结点的带权路径长度之和,记为           

技术分享

 
在下图所示的四棵二叉树,都有4个叶子结点,其权值分别1、2、3、4,他们的带权路径长度分别为:
 
(a)WPL = 1 x 2 + 2 x 2 + 3 x 2 + 4 X 2 = 20
(b)WPL = 1 x 1 + 2 x 2 + 3 x 3 + 4 x 3 = 28
(c)WPL  = 1 x 3 + 2 x 3 + 3 x 2 + 4 x 1 = 19
(d)WPL = 2 x 1 + 1 x 2 + 3 x 3 + 4 x 3 = 29
技术分享
其中,(c)图所示的二叉树的带权路径长度最小,这棵树就是哈夫曼树。可以验证,哈夫曼树的带权路径长度最小。
 
 
------------------------------------------------------------------------------------------------
 
哈夫曼树的构造
 
//(假设有n个权值,则构造出的哈夫曼树有n个叶子结点。)
对于已知的一组叶子的权值W 1 ,W 2...... ,W n
①首先把 n 个叶子结点看做 n 棵树(仅有一个结点的二叉树),把它们看做一个森林。
②在森林中把权值最小和次小的两棵树合并成一棵树,该树根结点的权值是两棵子树权值之和。这时森林中还有 n-1 棵树。
③重复第②步直到森林中只有一棵为止。此树就是哈夫曼树。
 
 n 个叶子构成的哈夫曼树其带权路径长度唯一吗?确实唯一。树形唯一吗?不唯一。因为将森林中两棵权值最小和次小的子棵合并时,哪棵做左子树,哪棵做右子树并不严格限制。选择不同时画出的树形有所不同,但 WPL 值相同。为了便于讨论交流在此提倡权值较小的做左子树, 权值较大的做右子。
 
  需要考虑的问题:
(1) 用什么样的存储结构;
(2) 怎么选择选择最小和次小权值的树;
(3) 怎么存放合成的新树
 
//存储结构:
 
顺序存储(静态数组/堆分配空间):数组长度为带权值的元素个数n额的俩倍,其中0号单元空置,1到n号单元存放只有当个节点的树(没有父节点,parent为0),
n+1之后的元素存放分别以前n个节点中的最小权重节点及次小权重节点为做做孩子和右孩子的双亲
 
 //节点:
typedef struct HuffmanTree{
    char data;
    int weight;
    int parent,lchild,rchild;  parent用来标记该节点是否为没有父节点的树(一个节点)
}hafnode,*haftree;
 
 
哈夫曼树的构造:(堆分配存储)
 
void select(haftree ht,int n,int &s1,int &s2){
 
     for(i=1;i<=n;i++){                //所有没有父节点的节点中以第一个节点为最小权重节点
        if(ht[i]->parent==0){
            s1=i;
            break;
        }
     }
     for(j=i+1;j<=n;j++){               //找到最小权值的节点
        if(ht[j]->parent==0&&ht[j]->weightweight)
            si=j;
     }
 
     for(i=1;i<=n;i++){
        if(ht[i]->parent==0&&i!=s1){    //所有没有父节点的节点中以第一个节点为次小权重节点
            s2=i;
            break;
        }
     }
     for(j=i+1;j<=n;j++){             //找到次小权值的节点
        if(ht[j]->parent==0&&ht[j]->weightweight)
            s2=j;
     }
 
}
 
void createhaftree(haftree &ht,int n,int quan[],char value[]){  //quan存放各节点的权重,value存放各节点                                                                                                        的值。0号单元均设空
   int s1,s2;
   m=2*n-1;
   ht=(Haftree)malloc(sizeof(hafnode)*(m+1));//构造2n个节点长度的数组,第一个设空。前n存n个有权值的                                                                                 节点,后n-1个存放每次最
                                             //小与次小的父节点。n-1次就可以将n个节点全部合并完成,故设n-1个节点长度
   for(i=1;i<=n;i++,ht++,quan++,value++){  //前n个节点进行初始化
    ht->data=value[i];
    ht->weight=quan[i];
    ht->parent=ht->lchild=ht->rchild=0;
   }
   for(i=n+1;i<=m;i++,ht++){  //后n-1个节点初始化
    ht->weight=0;
    ht->parent=ht->lchild=ht->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;
   }
}
 
 
注:构造得到的哈夫曼树存在数组中,前n个元素为叶子节点(带权节点),最后一个元素为根节点
-------------------------------------------------------------------------------------------------
哈夫曼树的应用:
 
1.用于通信编码
  在通信及数据传输中多采用二进制编码。为了使电文尽可能的缩短,可以对电文中每个字符
出现的次数进行统计。设法让出现次数多的字符的二进制码短些,而让那些很少出现的字符的二
进制码长一些。假设有一段电文,其中用到 4 个不同字符A,C,S,T,它们在电文中出现的
次数分别为 7 , 2 , 4 , 5 。把 7 , 2 , 4 , 5 当做 4 个叶子的权值构造哈夫曼树如图
 6.24(a) 所示。在树中令所有左分支取编码为 0 ,令所有右分支取编码为1。将从根结点起到某
 个叶子结点路径上的各左、右分支的编码顺序排列,就得这个叶子结点所代表的字符的二进制编
 码,如图 6.24(b) 所示。
 
    这些编码拼成的电文不会混淆,因为每个字符的编码均不是其他编码的前缀,这种编码称做前
缀编码。
 
  关于信息编码是一个复杂的问题,还应考虑其他一些因素。比如前缀编码每个编码的长度不相
等,译码时较困难。还有检测、纠错问题都应考虑在内。这里仅对哈夫曼树举了一个应用实例。
 

(1)编码:

算法思想: (将带权字符构建成哈弗曼树再进行编码,编码需从叶子节点发出一条走向根节点的路径,

约定左孩子分支为0,右孩子分支为1.)
 
void hafcoding(haftree ht,char *hc){  //hc指向一个字符指针数组,数组长度为n,n个指针分别指
                                      // 向n个叶子节点的字符进行字符编码后得到的编码串
    hc=(char *)malloc(n*sizeof(char *));
 
    char *cd;                           //cd指向一个字符数组,临时存放字符的编码串,因为是从叶子节点
    cd=(char *)malloc(n*sizeof(char));  //出发,则编码串是倒序逐个插入字符数组的,结尾有‘\0‘,读取时顺序。
    cd[n-1]=‘\0‘;
 
    for(i=1;i<=n;i++){   //对n个叶子节点的字符进行编码
        start=n-1;
        for(child=i,parent=ht[i]->parent;parent!=0;child=parent,parent=ht[parent]->parent){ //对叶子节点的字符进行编码
             if(child==ht[parent]->lchild)                                 //从叶子节点出发到根节点
                cd[--start]=‘0‘;                                          //child是为了判定该节点是其父节点的左孩子还是右孩子
             else
                cd[--start]=‘1‘;
        }
        hc[i-1]=(char *)malloc((n-start)*sizeof(char)); //字符指针数组的每个元素指向一个刚好存放其对应叶子节点编码串长度的字符数组
        strcpy(hc[i-1],&cd[start]);
    }
    free(cd);
}
 
(2)译码
 
算法:输入一串以哈夫曼编码方式编码的字符串,从根节点出发走向叶子节点,找到叶子节点
后输出该节点的字符然后继续读取编码串,直至读完
 
void decode(haftree ht){
   char s[100];
   gets(s);
   i=m;
   j=0;
   while(s[j]!=‘\0‘){    //遍历字符数组/编码串
      if(s[j]==‘0‘)
        i=ht[i]->lchild;   //走向左孩子
      else
        i=ht[i]->rchild    //走向右孩子
      if(ht[i]->lchild==NULL){   //看是否该节点为叶子节点
        printf("%c",ht[i]->data); //是的话输出,并返回根节点
        i=m;
      }
      j++;   //无论是否找到叶子节点都读取下一个编码串字符
   }
 
}

哈弗曼树的构建,哈夫曼编码、译码

标签:

原文地址:http://www.cnblogs.com/yujon/p/5467600.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!