哈夫曼算法
构造哈夫曼树的方法
1.根据n个给定权值{w1, w2, ..., wn}构成n棵二叉树的森林F = {T1, T2, ..., Tn}, 其中Ti只有一个带权为wi的根结点。
2.在F中选取两棵根结点的权值最小的树作为左右子树,构造一棵新的二叉树,且设置新的二叉树的根结点的权值为其左右子树上的根结点的权值之和。
3.在F中删除这两棵树,同时将新得到的二叉树加入森林中。
4.重复2.和3.直到森林中只有一棵树为止,这棵树就是哈夫曼树
性质
- 哈夫曼树的节点的度数为0 或 2,没有度为1的节点
- 包含n个叶子节点的哈夫曼树中共有2n-1个节点
- 包含n棵树的森林要经过n-1次合并才能形成n-1个新节点
哈夫曼树的存储结构
-
顺序存储结构
使用一维结构数组存储,数组元素定义如下:
typedef struct{
int weight; //节点的权重
int parent, lch, rch; //父节点,左右孩子节点的下标
}HTNode, *HuffmanTree
删除节点的方式:更新节点的父节点,及其父节点的左右孩子节点。遍历节点时,要从父节点值不为零的节点中选取。当数组中只有一个节点的父节点值为0时结束。
哈夫曼编码
要设计长度不等的编码,则必须使任意字符的编码都不是另一字符的编码的前缀——前缀编码
- 统计字符集中每个字符的平均出现频率。
- 以概率值作为权值构造哈夫曼树,概率越大的节点,路径越短。
- 在哈夫曼树的分枝上标上0 或 1:节点的左分支标0,右分支标1,把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符的编码。
1. 为什么哈夫曼编码能够保证是前缀编码?
因为没有一片树叶是另一片树叶的祖先,所以每个叶节点的编码就不可能是其他叶节点编码的前缀。
2. 为什么哈夫曼编码能够保证字符编码总长度最短?
因为哈夫曼树的带权路径长度最短,故字符编码的总长度最短。
- 哈夫曼编码是前缀码且是最优前缀码
3.解码的方法
- 构造哈夫曼树
- 依次读入二进制码
- 读入0,则走向左孩子;读入1,则走向右孩子
- 一旦到达某叶子节点时,可译出字符
- 然后再从根出发继续译码,直到结束
C语言实现
构造哈夫曼树
void CreateHuffmanTree(HuffmanTree* HT, int n, int* weights){
if(n <= 1)return;
*HT = (HuffmanTree)malloc(sizeof(struct HTNode)*(2*n-1));
HuffmanTree P = *HT;
for(int i = 0; i < 2*n-1; i++){
(P+i)->lch = -1;
(P+i)->rch = -1;
(P+i)->parent = -1;
}
for(int i = 0; i < n; i++){
(P+i)->weight = weights[i];
}
int min_idx = 0, nmin_idx = 0;
for(int i = n; i < 2*n-1; i++){
Select(HT, &min_idx, &nmin_idx, i);
(P+i)->lch = min_idx;
(P+i)->rch = nmin_idx;
(P+i)->weight = (P+min_idx)->weight + (P+nmin_idx)->weight;
(P+min_idx)->parent = i;
(P+nmin_idx)->parent = i;
}
}
构建哈夫曼编码
char** CreateHuffmanCode(HuffmanTree* HT, int n){
char** HC = malloc(sizeof(char*)*(n));
char* cd = (char*)malloc(sizeof(char)*n);
cd[n-1] = '\0';
HuffmanTree P = *HT;
for(int i = 0; i < n; i++){
int start = n-1, c = i, f = (P+i)->parent;
while(f != -1){
start--;
if((P+f)->lch == c)cd[start] = '0';
else cd[start] = '1';
c = f;
f = (P+f)->parent;
}
HC[i] = (char*)malloc(sizeof(char)*(n-start));
strcpy(HC[i], &cd[start]);
}
free(cd);
return HC;
}