在研究哈夫曼编码问题之前,我们先了解一下
哈夫曼树
。对于一棵
判别树
,通过计算其所有叶子结点的带权路径长度之和(WPL)来衡量其效率高低。
若使用定长编码传输字符,则编码如下:
则用定长编码方式进行编码需要3*10000=
30000位
。
下面则结合前面哈夫曼树WPL值最小性质我们试着对上例进行
编码
。
1.计各字符在字符串中出现概率为P(A)、P(B)、P(C)、P(D)、P(E)、P(F)、P(G),根据上面概率表重新定义权重如下:
2.根据权重构建
哈夫曼树
。
则用哈夫曼
编码
方式进行编码需要(45*1+13*3+12*3+16*3+9*4+5*5+6*5)*1000=
25900位
。
因此哈夫曼编码显然比定长码方案好,事实上,是该字符串最优编码方案。
下面我们简单看一下代码实现。
WPL=7*2+5*2+2*2+4*2=36
WPL=7*1+5*2+2*3+4*3=35
可以看出两棵判别树同样是a,b,c,d四个叶子节点,但是WPL值不等,也就说明两棵树判别效率不同, 哈夫曼树即WPL值最小 ,其实如上例后图便是一棵哈夫曼树。 哈夫曼树构建方法:1.根据给定的n个权值{w1,w2,…,wn}构成二叉树集合F={T1,T2,…,Tn},其中每棵二叉树Ti中只有一个带权为wi的根结点,其左右子树为空。
2.在F中选取两棵根结点权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为左右子树根结点的权值之和。
3.在F中删除这两棵树,同时将新的二叉树加入F中。
4.重复2、3,直到F只含有一棵树为止,得到哈夫曼树。
哈夫曼编码 则是基于哈夫曼树所作的编码方式。算法基本思路为: 1.根据实际问题抽象模型,分配不同权重,构建一棵哈夫曼树。 2.对于模型中任意个体,采用0,1串作为其代码,则每个个体的编码即哈夫曼树根带代表该个体的叶子节点的 路径 。代码中每一位的0或1分别作为指示某节点到左儿子或者右儿子的“路标”。 下面举一个具体的哈夫曼编码例子。 两地间通信发出一串包含10000个字符的字符串 AABCGDBABDEBFFAB... ,字符出现的频率如下表所示:字符 | A | B | C | D | E | F | G |
频率 (千次) | 45 | 13 | 12 | 16 | 9 | 5 | 6 |
A | B | C | D | E | F | G |
000 | 001 | 010 | 011 | 100 | 101 | 110 |
字符 | A | B | C | D | E | F | G |
权重 | 45 | 13 | 12 | 16 | 9 | 5 | 6 |
A | B | C | D | E | F | G |
1 | 010 | 011 | 001 | 0001 | 00000 | 00001 |
//haffman 树的结构typedef struct{ //叶子结点权值 unsigned int weight; //指向双亲,和孩子结点的指针 unsigned int parent; unsigned int lChild; unsigned int rChild;} Node, *HuffmanTree;//动态分配数组,存储哈夫曼编码typedef char *HuffmanCode;//选择两个parent为0,且weight最小的结点s1和s2的方法实现//n 为叶子结点的总数,s1和 s2两个指针参数指向要选取出来的两个权值最小的结点void select(HuffmanTree *huffmanTree, int n, int *s1, int *s2){ //标记 i int i = 0; //记录最小权值 int min; //遍历全部结点,找出单节点 for(i = 1; i <= n; i++) { //如果此结点的父亲没有,那么把结点号赋值给 min,跳出循环 if((*huffmanTree)[i].parent == 0) { min = i; break; } } //继续遍历全部结点,找出权值最小的单节点 for(i = 1; i <= n; i++) { //如果此结点的父亲为空,则进入 if if((*huffmanTree)[i].parent == 0) { //如果此结点的权值比 min 结点的权值小,那么更新 min 结点,否则就是最开始的 min if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight) { min = i; } } } //找到了最小权值的结点,s1指向 *s1 = min; //遍历全部结点 for(i = 1; i <= n; i++) { //找出下一个单节点,且没有被 s1指向,那么i 赋值给 min,跳出循环 if((*huffmanTree)[i].parent == 0 && i != (*s1)) { min = i; break; } } //继续遍历全部结点,找到权值最小的那一个 for(i = 1; i <= n; i++) { if((*huffmanTree)[i].parent == 0 && i != (*s1)) { //如果此结点的权值比 min 结点的权值小,那么更新 min 结点,否则就是最开始的 min if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight) { min = i; } } } //s2指针指向第二个权值最小的叶子结点 *s2 = min;}//创建哈夫曼树并求哈夫曼编码的算法如下,w数组存放已知的n个权值void createHuffmanTree(HuffmanTree *huffmanTree, int w[], int n){ //m 为哈夫曼树总共的结点数,n 为叶子结点数 int m = 2 * n - 1; //s1 和 s2 为两个当前结点里,要选取的最小权值的结点 int s1; int s2; //标记 int i; // 创建哈夫曼树的结点所需的空间,m+1,代表其中包含一个头结点 *huffmanTree = (HuffmanTree)malloc((m + 1) * sizeof(Node)); //1--n号存放叶子结点,初始化叶子结点,结构数组来初始化每个叶子结点,初始的时候看做一个个单个结点的二叉树 for(i = 1; i <= n; i++) { //其中叶子结点的权值是 w【n】数组来保存 (*huffmanTree)[i].weight = w[i]; //初始化叶子结点(单个结点二叉树)的孩子和双亲,单个结点,也就是没有孩子和双亲,==0 (*huffmanTree)[i].lChild = 0; (*huffmanTree)[i].parent = 0; (*huffmanTree)[i].rChild = 0; }// end of for //非叶子结点的初始化 for(i = n + 1; i <= m; i++) { (*huffmanTree)[i].weight = 0; (*huffmanTree)[i].lChild = 0; (*huffmanTree)[i].parent = 0; (*huffmanTree)[i].rChild = 0; } printf("\n HuffmanTree: \n"); //创建非叶子结点,建哈夫曼树 for(i = n + 1; i <= m; i++) { //在(*huffmanTree)[1]~(*huffmanTree)[i-1]的范围内选择两个parent为0 //且weight最小的结点,其序号分别赋值给s1、s2 select(huffmanTree, i-1, &s1, &s2); //选出的两个权值最小的叶子结点,组成一个新的二叉树,根为 i 结点 (*huffmanTree)[s1].parent = i; (*huffmanTree)[s2].parent = i; (*huffmanTree)[i].lChild = s1; (*huffmanTree)[i].rChild = s2; //新的结点 i 的权值 (*huffmanTree)[i].weight = (*huffmanTree)[s1].weight + (*huffmanTree)[s2].weight; printf("%d (%d, %d)\n", (*huffmanTree)[i].weight, (*huffmanTree)[s1].weight, (*huffmanTree)[s2].weight); } printf("\n");}//哈夫曼树建立完毕,从 n 个叶子结点到根,逆向求每个叶子结点对应的哈夫曼编码void creatHuffmanCode(HuffmanTree *huffmanTree, HuffmanCode *huffmanCode, int n){ //指示biaoji int i; //编码的起始指针 int start; //指向当前结点的父节点 int p; //遍历 n 个叶子结点的指示标记 c unsigned int c; //分配n个编码的头指针 huffmanCode=(HuffmanCode *)malloc((n+1) * sizeof(char *)); //分配求当前编码的工作空间 char *cd = (char *)malloc(n * sizeof(char)); //从右向左逐位存放编码,首先存放编码结束符 cd[n-1] = '\0'; //求n个叶子结点对应的哈夫曼编码 for(i = 1; i <= n; i++) { //初始化编码起始指针 start = n - 1; //从叶子到根结点求编码 for(c = i, p = (*huffmanTree)[i].parent; p != 0; c = p, p = (*huffmanTree)[p].parent) { if( (*huffmanTree)[p].lChild == c) { //从右到左的顺序编码入数组内 cd[--start] = '0'; //左分支标0 } else { cd[--start] = '1'; //右分支标1 } }// end of for //为第i个编码分配空间 huffmanCode[i] = (char *)malloc((n - start) * sizeof(char)); strcpy(huffmanCode[i], &cd[start]); } free(cd); //打印编码序列 for(i = 1; i <= n; i++) { printf("HuffmanCode of %3d is %s\n", (*huffmanTree)[i].weight, huffmanCode[i]); } printf("\n");}int main(void){ HuffmanTree HT; HuffmanCode HC; int *w,i,n,wei,m; printf("\nn = " ); scanf("%d",&n); w=(int *)malloc((n+1)*sizeof(int)); printf("\ninput the %d element's weight:\n",n); for(i=1; i<=n; i++) { printf("%d: ",i); fflush(stdin); scanf("%d",&wei); w[i]=wei; } createHuffmanTree(&HT, w, n); creatHuffmanCode(&HT,&HC,n); return 0;}
根据分析可得,关于n个字符的哈夫曼算法的时间复杂度为