哈夫曼树和哈夫曼编码

哈夫曼树是一种带权路径长度最小的二叉树,也叫最优二叉树。

树的带权路径长度指的是:

假设二叉树有n个叶子结点,每个叶子结点都带有权重,那么这棵二叉树的带权路径长度和为:从根节点到每一个叶子结点的路径长度*叶子结点权重的和,具体公式如下:

Wk为第k个叶子结点的权重,Lk为根节点到第k个叶子结点的路径长度。

 

相关术语

1.树的路径长度:从根节点到各个叶子结点路径长度之和;

2.结点带权路径长度:根节点到该节点的路径长度乘以该节点权重;

最优二叉树:树的带权路径长度最小的二叉树。

举例子,2,3,5,9这四个结点可以构造几个不同的二叉树,如下:

5棵树的带权路径长度分别为:

(a)WPL=2×2+3×2+5×2+9×2=38
(b)WPL=2×3+3×3+5×2+9×1=34
(c)WPL=2×2+3×3+5×3+9×1=37
(d)WPL=9×3+5×3+3×2+2×1=50
(e)WPL=2×1+3×3+5×3+9×2=44
(b)的权重路径长度和最小,它的特点是,权重大的结点靠近根节点,权重小的结点远离根节点。

构成最优二叉树的方法由哈夫曼提出,因此也叫哈夫曼树。哈夫曼树的建立步骤如下:

1.给定n个权重为W1,W2,......,Wn的结点,每一个结点都能看成是只有一个叶子结点的二叉树,从而得到一个二叉树的集合F={T1,T2,......Tn};

2.从F中选择根节点权重最小和次小的两棵树,作为一颗树的左右子树(谁左谁右都行),从而构建了一颗新的二叉树,此二叉树的根节点权重为左右子树根节点权重之和;

3.从集合F中删除已经使用了的最小和次小树,并将新构建的二叉树插入集合中;

4.重复2/3步骤,知道集合F只剩下一棵树,这棵树便是要构造的哈夫曼树。

举例:

下面以例6-11中的叶结点权值:2、3、5、9为例,介绍哈夫曼树的构造过程。
(1)取出权值最小的2和3,构成一棵二叉树,如图6-36(a)所示,其权值之和为5。
(2)再取出权值最小的5和5,构成一棵二叉树,如图6-36(b)所示,其权值之和为10。
(3)再取出权值最小的9和10,构成一棵二叉树,如图6-36(c)所示,其权值之和为19。

哈夫曼树构造的具体实现

可以设置一个结构数组HFMT,用于保存哈夫曼树中各结点信息,由二叉树性质可知,具有n个结点的哈夫曼树共有2n-1个结点,所以需要2n-1长度的数组,其结构体形式如下:

分别保存权重、左孩子结点在数组中的下标、左孩子结点在数组中的下标、此节点是否已经加入到要建立的哈夫曼树中,PARENT初始为-1,如结点已加入哈夫曼树,则parent的值为其父节点在数组中的下标。

构建哈夫曼树时,首先将由N个字符形成的N各叶节点存放到数组HFMT的前N个分量中,然后根据哈夫曼方法的基本思想,不断将权值最小的子树合并成一个较大的子树,每次构成的新子树的根节点顺序存放到HFMT数组中的前N个分量后面。

具体代码如下:

#include <iostream>
#include <iomanip>
using namespace std;
struct node{//数组结点
    int weight;
    int lchild,rchild,parent;
    node():weight(0),lchild(-1),rchild(-1),parent(-1){}
};
void selectmin(const node HFMT[],int k,int &index1,int &index2){
    for(int i=0;i<k;++i){
        if(HFMT[i].parent==-1){//index1必须是还未使用的树的根结点下标
            index1=i;
            break;
        }
    }
    for(int i=0;i<k;++i){//找到权重最小的INDEX作为index1
        if(HFMT[i].parent==-1 && HFMT[index1].weight>HFMT[i].weight)
            index1=i;
    }
    for(int i=0;i<k;++i){//index2必须是还未使用的树的根结点下标,且不能是index1
        if(HFMT[i].parent==-1 && i!=index1){
            index2=i;
            break;
        }
    }
    for(int i=0;i<k;++i){//index2为权重次小的根节点的下标
        if(HFMT[i].parent==-1 && i!=index1 && HFMT[index2].weight>HFMT[i].weight)
            index2=i;
    }
}
void HuffmanTree(node HFMT[],int w[],int n){
    for(int i=0;i<n;++i){
        HFMT[i].weight=w[i];
    }
    for(int k=n;k<2*n-1;++k){
        int index1,index2;
        selectmin(HFMT,k,index1,index2);
        HFMT[index1].parent=k;
        HFMT[index2].parent=k;
        HFMT[k].lchild=index1;
        HFMT[k].rchild=index2;
        HFMT[k].weight=HFMT[index1].weight+HFMT[index2].weight;
    }
}
void print(node HFMT[],int n){
    printf("%s\n","index weight parent lchild rchild");
    cout << left;
    for(int i=0;i<n;++i){
        cout << setw(5) << i << " ";
        cout << setw(6) << HFMT[i].weight << " ";
        cout << setw(6) << HFMT[i].parent << " ";
        cout << setw(6) << HFMT[i].lchild << " ";
        cout << setw(6) << HFMT[i].rchild << endl;
    }
}
int main() {
    int vec[]={2,3,5,9};
    int N=sizeof(vec)/ sizeof(int);
    node *hufftree=new node[2*N-1];
    HuffmanTree(hufftree,vec,N);
    print(hufftree,2*N-1);
    return 0;
}

输出结果:

index weight parent lchild rchild
0     2      4      -1     -1    
1     3      4      -1     -1    
2     5      5      -1     -1    
3     9      6      -1     -1    
4     5      5      0      1     
5     10     6      2      4     
6     19     -1     3      5 

哈夫曼树的应用场景之哈夫曼编码

数据通信中经常需要将要传输的字符编码成二进制01字符串,这一过程叫做编码。

如果在编码中考虑字符出现频次,对于经常出现的字符,如果使用较短的01字符串,那么会增加传输效率,将哈夫曼树运用到编码中可以解决这一问题,具体方法如下:

1.统计字符串集合{d1,d2,......dn}中各字符串出现频次{w1,w2,......wn};

2.以字符串作为树的结点的值,以频次作为结点的权重,构建哈夫曼树;

3.规定哈夫曼树左分支代表0,右分支代表1,则从根节点到每个叶子结点路径上的01,就构成了该节点的哈夫曼编码值。

举例子:

 设有A、B、C、D、E、F六个数据项,其出现的频度分别为6、5、4、3、2、1,试构造一棵哈夫曼树,并确定它们的哈夫曼编码。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值