哈夫曼树和哈夫曼编码
哈夫曼树─即最优二叉树,带权路径长度最小的二叉树,经常应用于数据压缩。 在计算机信息处理中,“哈夫曼编码”是一种一致性编码法(又称“熵编码法”),用于数据的无损耗压缩。这一术语是指使用一张特殊的编码表将源字符(例如某文件中的一个符号)进行编码。这张编码表的特殊之处在于,它是根据每一个源字符出现的估算概率而建立起来的(出现概率高的字符使用较短的编码,反之出现概率低的则使用较长的编码,这便使编码之后的字符串的平均期望长度降低,从而达到无损压缩数据的目的)。这种方法是由David.A.Huffman发展起来的。
树的带权路径长度记为
WPL= (W1*L1+W2*L2+W3*L3+...+Wn*Ln)
,N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。可以证明哈夫曼树的WPL是最小的。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
哈夫曼编码步骤:
一、对给定的n个权值{W1,W2,W3,...,Wi,...,Wn}构成n棵二叉树的初始集合F= {T1,T2,T3,...,Ti,...,Tn},其中每棵二叉树Ti中只有一个权值为Wi的根结点,它的左右子树均为空。(为方便在计算机上实现算 法,一般还要求以Ti的权值Wi的升序排列。)
二、在F中选取两棵根结点权值最小的树作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和。
三、从F中删除这两棵树,并把这棵新的二叉树同样以升序排列加入到集合F中。
四、重复二和三两步,直到集合F中只有一棵二叉树为止。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
简易的理解就是,假如我有A,B,C,D,E五个字符,出现的频率(即权值)分别为5,4,3,2,1,那么我们第一步先取两个最小权值作为左右子树构造一个新树,即取1,2构成新树,其结点为1+2=3,如图:
虚线为新生成的结点,第二步再把新生成的权值为3的新树升序方式放到剩下的集合中。1、2两棵树删除,所以集合变成{5,4,3,3},再根据第二步,取最小的两个权值构成新树,如图:
集合变成{6,5,4}。再依次建立哈夫曼树,取4和5,再取6和9,如下图:
其中各个权值替换对应的字符即为下图:
左子树0 右子树1.ABCDE一定是叶子结点,所对应的哈弗曼编码就是从根节点“读”下来。 所以各字符对应的编码为:A->11,B->10,C->00,D->011,E->010
哈夫曼编码是一种无前缀编码。解码时不会混淆。其主要应用在数据压缩,加密解密等场合。
说到这里我要插一句:
A,B,C,D,E五个字符,由于计算机只能识别0,1,所以可以用 0 1 10 11 100 来表示,然而仅仅这样是不行的,并不能区分。
----我来举个例子说明为什么不能区分:随便写一段:111001011. 解码可能就成为AAABBABAA。仔细想想应该就懂了。
所以,若要区分,可以使用定长操作码,就是每条需要3位(ABCDE则对应000,001,010,011,100)。每个字符都需要3位这无疑是一种浪费(2^3=8,3位可以表示8种数据 ,还有3条没有作用),如何进行优化呢?便有了哈夫曼编码。
还是这道题,我们用数字对比一下哈夫曼编码的作用。
上面算出来的结果是:ABCDE分别对应11,10,00,011,010.这是可以区分的。
----我同样来举个例子说明为什么能区分:随便写一段:111001011。解码就是:ABEA。这是必然的。
所以,我们不需要用每条3位去编码了,我们来计算一下节省了多少位。
假如ABCDE是共有1000条指令,根据他们的权值(5,4,3,2,1)计算他们的频率为(5/15,4/15,3/15,2/15,1/15)。
计算得在这1000条中, A大约有333条,B大约有266条,C大约有200条,D大约有133条,E大约有66条。(血崩!!我为什么不找个好计算一点的例子)
而上面刚求出的ABCDE所占位数分别为(2,2,2,3,3),计算总位数为2*333+2*266+2*200+3*133+3*66=2195
看见了吧! 2195<3000(3*1000) 效果还是很明显滴~
C语言代码实现(代码来源于网络):
/*-------------------------------------------------------------------------
* Name: 哈夫曼编码源代码。
* Date: 2011.04.16
* Author: Jeffrey Hill+Jezze(解码部分)
* 在 Win-TC 下测试通过
* 实现过程:着先通过 HuffmanTree() 函数构造哈夫曼树,然后在主函数 main()中
* 自底向上开始(也就是从数组序号为零的结点开始)向上层层判断,若在
* 父结点左侧,则置码为 0,若在右侧,则置码为 1。最后输出生成的编码。
*------------------------------------------------------------------------*/
#include
#include
#define MAXBIT 100
#define MAXVALUE 10000
#define MAXLEAF 30
#define MAXNODE MAXLEAF*2 -1
typedef struct
{
int bit[MAXBIT];
int start;
} HCodeType; /* 编码结构体 */
typedef struct
{
int weight;
int parent;
int lchild;
int rchild;
int value;
} HNodeType; /* 结点结构体 */
/* 构造一颗哈夫曼树 */
void HuffmanTree (HNodeType HuffNode[MAXNODE], int n)
{
/* i、j: 循环变量,m1、m2:构造哈夫曼树不同过程中两个最小权值结点的权值,
x1、x2:构造哈夫曼树不同过程中两个最小权值结点在数组中的序号。*/
int i, j, m1, m2, x1, x2;
/* 初始化存放哈夫曼树数组 HuffNode[] 中的结点 */
for (i=0; i<2*n-1; i++)
{
HuffNode[i].weight = 0;//权值
HuffNode[i].parent =-1;
HuffNode[i].lchild =-1;
HuffNode[i].rchild =-1;
HuffNode[i].value=i; //实际值,可根据情况替换为字母
} /* end for */
/* 输入 n 个叶子结点的权值 */
for (i=0; i {printf ("Please input weight of leaf node %d: \n", i);
scanf ("%d", &HuffNode[i].weight);
} /* end for */
/* 循环构造 Huffman 树 */
for (i=0; i {m1=m2=MAXVALUE; /* m1、m2中存放两个无父结点且结点权值最小的两个结点 */
x1=x2=0;/* 找出所有结点中权值最小、无父结点的两个结点,并合并之为一颗二叉树 */
for (j=0; j {if (HuffNode[j].weight < m1 && HuffNode[j].parent==-1)
{m2=m1;x2=x1;m1=HuffNode[j].weight;x1=j;}else if (HuffNode[j].weight < m2 && HuffNode[j].parent==-1){m2=HuffNode[j].weight;x2=j;}} /* end for */
/* 设置找到的两个子结点 x1、x2 的父结点信息 */
HuffNode[x1].parent = n+i;HuffNode[x2].parent = n+i;HuffNode[n+i].weight = HuffNode[x1].weight + HuffNode[x2].weight;HuffNode[n+i].lchild = x1;HuffNode[n+i].rchild = x2;printf ("x1.weight and x2.weight in round %d: %d, %d\n", i+1, HuffNode[x1].weight, HuffNode[x2].weight); /* 用于测试 */printf ("\n");
} /* end for */
/* for(i=0;i
{
printf(" Parents:%d,lchild:%d,rchild:%d,value:%d,weight:%d\n",HuffNode[i].parent,HuffNode[i].lchild,HuffNode[i].rchild,HuffNode[i].value,HuffNode[i].weight);
}*///测试} /* end HuffmanTree */
//解码
void decodeing(char string[],HNodeType Buf[],int Num){int i,tmp=0,code[1024];
int m=2*Num-1;
char *nump;
char num[1024];
for(i=0;istring);i++) {if(string[i]=='0')num[i]=0;else
num[i]=1;}i=0;nump=&num[0];while(nump<(&num[strlen(string)])){tmp=m-1;while((Buf[tmp].lchild!=-1)&&(Buf[tmp].rchild!=-1))
{if(*nump==0)
{tmp=Buf[tmp].lchild ;}else tmp=Buf[tmp].rchild;
nump++;}printf("%d",Buf[tmp].value);}}int main(void){HNodeType HuffNode[MAXNODE]; /* 定义一个结点结构体数组 */
HCodeType HuffCode[MAXLEAF], cd; /* 定义一个编码结构体数组, 同时定义一个临时变量来存放求解编码时的信息 */
int i, j, c, p, n;
char pp[100];
printf ("Please input n:\n");
scanf ("%d", &n);
HuffmanTree (HuffNode, n);for (i=0; i < n; i++)
{cd.start = n-1;c = i;p = HuffNode[c].parent;while (p != -1) /* 父结点存在 */{if (HuffNode[p].lchild == c)
cd.bit[cd.start] = 0;else
cd.bit[cd.start] = 1;cd.start--; /* 求编码的低一位 */
c=p;p=HuffNode[c].parent; /* 设置下一循环条件 */
} /* end while */
/* 保存求出的每个叶结点的哈夫曼编码和编码的起始位 */
for (j=cd.start+1; j { HuffCode[i].bit[j] = cd.bit[j];}HuffCode[i].start = cd.start;} /* end for */
/* 输出已保存好的所有存在编码的哈夫曼编码 */
for (i=0; i {printf ("%d 's Huffman code is: ", i);
for (j=HuffCode[i].start+1; j < n; j++)
{printf ("%d", HuffCode[i].bit[j]);
}printf(" start:%d",HuffCode[i].start);
printf ("\n");
}/* for(i=0;i
for(j=0;j
{
printf ("%d", HuffCode[i].bit[j]);
}
printf("\n");
}*/
printf("Decoding?Please Enter code:\n");
scanf("%s",&pp);
decodeing(pp,HuffNode,n);getch();return 0;
}
图片案例 转载自:http://www.cnblogs.com/Jezze/archive/2011/12/23/2299884.html