文字描述
结点的路径长度
从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上的分支数目称作路径长度。
树的路径长度
从树根到每一个结点的路径长度之和叫树的路径长度。
结点的带权路径长度
从该结点到树根之间的路径长度与结点上权的乘积
树的带权路径长度
所有叶子结点的带权路径长度之和
最优二叉树或哈夫曼树
假设有n个权值{w1,w2, … ,wn},试构造一颗有n个叶子结点的二叉树,每个叶子结点带权wi,则其中带权路径长度WPL最小的二叉树称作最优二叉树或哈夫曼树。
哈夫曼算法
目的:构造一颗哈夫曼树
实现:
(1) 根据给定的n个权值{w1,w2, … ,wn}构成n棵二叉树的集合F={T1,T2,…,Tn},其中每棵二叉树Ti中只有一个带权为wi的根结点,其左右子树均为空。
(2) 在F中选取两颗根结点的权值最小的树作为左右子树构造一颗新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
(3) 在F中删除这两颗树,同时将新得到的二叉树加入F中
(4) 重复(2)和(3),直到F只含一颗树为止。这颗树便是哈夫曼树。
哈夫曼编码
假设A、B、C、D的编码分别为00,01,10和11,则字符串’ABACCDA‘的电文便是’00010010101100’,对方接受时,可按二位一分进行译码。但有时希望电文总长尽可能的短, 如果让电文中出现次数较多的字符采用尽可能短的编码,则传送电文的总长鞭可减少。但是设计长短不等的编码,则必须是任一字符的编码都不是另一个字符的编码的前缀,以避免同样子电文串有多种译法,这种编码叫前缀编码。
又如何得到使电文总长最短的二进制前缀编码呢?假设字符串中有n种字符,每种字符在电文中出现的次数作为权,设计一颗哈夫曼树。左子树为0,右子树为1,走一条从根到叶子结点的路径进行编码。
哈夫曼译码
译码的过程是分解电文字符串,从根出发,按字符‘0‘或’1‘确定找左孩子或右孩子,直至叶子结点,便求得该子串相应的字符。
示意图
算法分析
略
代码实现
1 /* 2 ./a.out 5 29 7 8 14 23 3 11 3 4 1001111100110001110 5 */ 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 10 #define DEBUG 11 //结点的最大个数 12 #define MAX_SIZE 50 13 14 //哈夫曼树和哈夫曼结点的存储结构 15 typedef struct{ 16 unsigned int weight; 17 unsigned int parent, lchild, rchild; 18 }HNode, *HuffmanTree; 19 20 //哈夫曼编码表的存储结构 21 typedef char **HuffmanCode; 22 23 /* 24 * 在HT[1, ..., l]中选择parent为0且weight最小的两个结点,其序号分别为min1,min2 25 */ 26 int Select(HuffmanTree HT, int l, int *min1, int *min2){ 27 int i = 0; 28 *min1 = *min2 = -1; 29 30 //找parent为0且weight最小的min1 31 for(i=1; i<=l; i++){ 32 if(!HT[i].parent){ 33 if(*min1<0){ 34 *min1 = i; 35 }else if(HT[*min1].weight > HT[i].weight){ 36 *min1 = i; 37 }else{ 38 //other 39 } 40 }else{ 41 //other 42 } 43 } 44 45 //找parent为0且weight最小的min2 46 for(i = 1; i<=l; i++){ 47 if(!HT[i].parent && i!=*min1){ 48 if(*min2<0){ 49 *min2 = i; 50 }else if(HT[*min2].weight > HT[i].weight){ 51 *min2 = i; 52 }else{ 53 //other 54 } 55 }else{ 56 //other 57 } 58 } 59 60 //确保min1<min2 61 if(*min1 > *min2){ 62 i = *min1; 63 *min1 = *min2; 64 *min2 = i; 65 } 66 67 if((*min1<0) && (*min2<0)){ 68 return -1; 69 }else{ 70 return 0; 71 } 72 } 73 74 /* 75 * 创建一颗哈夫曼树 76 * 77 * w存放n个字符的权值(权值都为非负数),构造哈夫曼树HT, L表示哈夫曼树的长度(正常情况下为2*n-1) 78 */ 79 void HuffmanCreate(int w[], int n, HuffmanTree *HT, int *L){ 80 if(n<=1){ 81 return; 82 } 83 int m = 2*n-1, i = 0, s1 = 0, s2 = 0; 84 //0号单元不用 85 (*HT) = (HuffmanTree)malloc((m+1)*sizeof(HNode)); 86 HuffmanTree p = *HT; 87 *L = m; 88 89 //给哈夫曼树的叶子结点赋值 90 for(p=(*HT+1), i = 0; i<n; i++){ 91 p[i].weight = w[i]; 92 p[i].parent = p[i].lchild = p[i].rchild = 0; 93 } 94 95 //给哈夫曼树的非叶子结点赋值 96 for(i=n+1; i<=m; ++i){ 97 //在HT[1,...,i-1]中选择parent为0且weight最小的两个结点,其序号分别为s1和s2 98 if( Select(*HT, i-1, &s1, &s2) < 0){ 99 break; 100 } 101 //用s1和s2作为左右子树构造一颗新的二叉树 102 (*HT)[s1].parent = (*HT)[s2].parent = i; 103 (*HT)[i].lchild = s1; 104 (*HT)[i].rchild = s2; 105 //置新的二叉树的根结点的权值为其左右子树上根结点的权值之和 106 (*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight; 107 } 108 return; 109 } 110 111 /* 112 * 哈夫曼编码 113 * 114 * 从叶子到根逆向求每个字符的哈夫曼编码 115 */ 116 void HuffmanEncoding_1(HuffmanTree HT, HuffmanCode *HC, int n){ 117 //分配n个字符编码的头指针向量 118 *HC = (HuffmanCode)malloc((n+1)*sizeof(char *)); 119 //分配求编码的工作空间 120 char *cd = (char*)malloc(n+sizeof(char)); 121 //编码结束符 122 cd[n-1] = '