哈夫曼树和哈夫曼编码

.一.先构造哈夫曼树,在编码
二.构造哈夫曼树的步骤:
1.先给队列进行排序
2.重队列中取出最小的两个数(前两个)
3.这两个数相加的和与这两个构成一个二叉树,把和的结果插入原来的队列,原来的两个树删除
4.在对队列进行排序,找出最小的两个数。。。。。
5.构造完哈夫曼树以后,左支为0,右支为1,每个结点都可以用0 1 来表示
6.用 0 1 表示就是哈夫曼编码
7.根据哈夫曼树,我们可以得到哈夫曼编码。哈夫曼编码是可变字长编码的一种形式,该编码依照字符出现频率来构造平均长度最短的码字,有时也叫做最佳编码。
/*********************************************
以上为自己所想,以下为转载
//*********************************************
概念
哈夫曼树,也叫最优二叉树。在含有给定的n个带权叶子结点的二叉树中,WPL最小的树。其中,结点的权指的是某种特定含义的数值;带权路径长度值根到结点的路径长度乘以结点权值;树的带权路径长度(WPL)是指树中所有叶子结点的带权路径长度之和。

构造
根据哈夫曼算法,叙述如下:

1.根据给定的n个权值{w1,w2,…,wn}构成的n棵二叉树集合F={T1,T2,…,Tn},其中每棵二叉树Ti中只有一个带权为wi的根结点,其左右子树均为空。

2.在F中选取两棵根结点权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上的根结点的权值之和。

3.在F中删除这两棵树,同时将新得到的二叉树加入到F中。

4.重复(2)和(3),知道F只含有一棵树为止。则这棵树便是哈夫曼树。

其中关键步骤是,每次选两个根结点权值最小的树合并,并将两者的权值之和作为新的根结点的权值。而从哈夫曼算法可以看到,哈夫曼树不唯一,但WPL必是最小值。

如下图所示,给出ABCDEF六个字符,字符下方标出了各自所占的权值,通过上面的算法一步步得出哈夫曼树:

哈夫曼编码
哈夫曼树有什么用?根据哈夫曼树,我们可以得到哈夫曼编码。哈夫曼编码是可变字长编码的一种形式,该编码依照字符出现频率来构造平均长度最短的码字,有时也叫做最佳编码。

例如,英文当中有26个英文字母,每个字母出现的频率是不一样的:

如果采用常规的固定编码,每个字符所对应的二进制编码可能会更加容易表示出来,但是在实际上,所造成的负面作用是显而易见的,那就是句子冗长。进行快速远距离通信的主要手段是电报,即将需要传送的文字转换成二进制的字符组成的字符串。在传送电文时,总长应尽可能的短,这样传递更有效率。尤其是在传递大量信息,或是关键情报时,电报的长度就显得十分重要。所以,哈夫曼编码就专门处理这类问题,首先根据字符出现的频率分配权值,构造哈夫曼树,得到哈夫曼编码的方式十分简单,我们在哈夫曼树的每一个左分支上注“0”,右分支上注“1”,从根结点走到一个叶子结点,得到一组二进制序列,这个序列就是就是该叶子结点对应字符的哈夫曼编码。

如何利用代码具体实现为给定权值的字符编码?下面通过例子来说一下大致流程:假定一个通信系统中只可能出现8种字符,其概率分别为0.05,0.29,0.07,0.08,0.14,0.23,0.03,0.11,试设计哈夫曼代码。

首先我们利用动态分配数组存储哈夫曼树,该数组的每一个元素表示这样一个结构体,它存放哈夫曼树中一个结点的信息:即权值、双亲结点、左孩子、右孩子的位置(及数组的下标)。那么,在本例中,我们有8个待编码的字符,根据哈夫曼树的性质可知,我们应该构建2*8-1=15个结点的哈夫曼树,即定义一个存放15个结构体的数组。我们应该给哈夫曼树中每一个叶子结点赋权值,然后其他数据域赋无效值。那么对于本题,应对数组中下标为1到8的元素权值域赋值,其他数据域赋0,对于下标8到15的元素所有数据域均赋值为0,对应图a。

之后构建哈夫曼树,在之前的算法描述中,我们可以知道,应将两个权值最小的结点合并,并将两者的权值之和作为新的根结点的权值。那么具体到这个题目来说,举例说明。第一次时,先扫描数组前8个结点,选择两个权值最小的结点,将它们的双亲域置为9,意思是数组下标为9的地方存放这两个结点的双亲结点,左、右孩子域仍为0(因为这两个结点是叶子结点)。然后将它们的双亲结点(即数组下标为9的结点)的左、右孩子域分别置为那两个权值最小的结点所在的数组下标,并将该结点的权值域设置为那两个权值最小的结点的权值域之和,第一次构建就完成了。我们应通过适当的循环来重复这一过程,直到构建出一个完整的哈夫曼树,最终构建完如图b。

构建好哈夫曼树之后,就可以求哈夫曼编码。同之前一样,动态分配数组存储哈夫曼编码表,这个数组的每一个元素是char*类型,即每一个元素存储一个字符串。通过循环,每一次从不同叶子到根逆向求编码,遵循之前提到的左0右1原则,每次构建出一个叶子结点的编码,通过strcpy函数将结果拷贝到之前的数组当中,该操作对应图c。
————————————————
版权声明:本文为CSDN博主「是小飞象啦啦啦」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_52290532/article/details/110498397

//算法5.11 根据赫夫曼树求赫夫曼编码 #include using namespace std; typedef struct { int weight; int parent,lchild,rchild; }HTNode,*HuffmanTree; typedef char **HuffmanCode; void Select(HuffmanTree HT,int len,int &s1,int &s2) { int i,min1=0x3f3f3f3f,min2=0x3f3f3f3f;//先赋予最大值 for(i=1;i<=len;i++) { if(HT[i].weight<min1&&HT[i].parent==0) { min1=HT[i].weight; s1=i; } } int temp=HT[s1].weight;//将原值存放起来,然后先赋予最大值,防止s1被重复选择 HT[s1].weight=0x3f3f3f3f; for(i=1;i<=len;i++) { if(HT[i].weight<min2&&HT[i].parent==0) { min2=HT[i].weight; s2=i; } } HT[s1].weight=temp;//恢复原来的值 } //用算法5.10构造赫夫曼树 void CreatHuffmanTree(HuffmanTree &HT,int n) { //构造赫夫曼树HT int m,s1,s2,i; if(n<=1) return; m=2*n-1; HT=new HTNode[m+1]; //0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根结点 for(i=1;i<=m;++i) //将1~m号单元中的双亲、左孩子,右孩子的下标都初始化为0 { HT[i].parent=0; HT[i].lchild=0; HT[i].rchild=0; } cout<<"请输入叶子结点的权值:\n"; for(i=1;i>HT[i].weight; /*――――――――――初始化工作结束,下面开始创建赫夫曼树――――――――――*/ for(i=n+1;i<=m;++i) { //通过n-1次的选择、删除、合并来创建赫夫曼树 Select(HT,i-1,s1,s2); //在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的结点, // 并返回它们在HT中的序号s1和s2 HT[s1].parent=i; HT[s2].parent=i; //得到新结点i,从森林中删除s1,s2,将s1和s2的双亲域由0改为i HT[i].lchild=s1; HT[i].rchild=s2 ; //s1,s2分别作为i的左右孩子 HT[i].weight=HT[s1].weight+HT[s2].weight; //i 的权值为左右孩子权值之和 } //for } // CreatHuffmanTree void CreatHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n) { //从叶子到根逆向求每个字符的赫夫曼编码,存储在编码表HC中 int i,start,c,f; HC=new char*[n+1]; //分配n个字符编码的头指针矢量 char *cd=new char[n]; //分配临时存放编码的动态数组空间 cd[n-1]='\0'; //编码结束符 for(i=1;i<=n;++i) { //逐个字符求赫夫曼编码 start=n-1; //start开始时指向最后,即编码结束符位置 c=i; f=HT[i].parent; //f指向结点c的双亲结点 while(f!=0) { //从叶子结点开始向上回溯,直到根结点 --start; //回
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值