哈夫曼编码
1、介绍
在计算机数据处理中,哈夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现几率的方法得到的,出现几率高的字母使用较短的编码,反之出现几率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而达到无损压缩数据的目的。
例如,在英文中,e的出现几率最高,而z的出现概率则最低。当利用哈夫曼编码对一篇英文进行压缩时,e极有可能用一个比特来表示,而z则可能花去25个比特(不是26)。用普通的表示方法时,每个英文字母均占用一个字节,即8个比特。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。倘若我们能实现对于英文中各个字母出现概率的较准确的估算,就可以大幅度提高无损压缩的比例。
哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1L1+W2L2+W3L3+…+WnLn),N个权值Wi(i=1,2,…n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,…n)。可以证明哈夫曼树的WPL是最小的。
本文求解的是,字母表{A, C, G, T},它们在一个长串中出现的频率分别是0.31, 0.2, 0.09, 0.4。试给出四个字母的Huffman编码。
2、代码
#include <stdio.h>
#include <malloc.h>
#define N 4 //数组长度
char code[N]; //存储哈夫曼编码
typedef struct Node { //哈夫曼树结构体
int data; //权值
char c; //字符
struct Node *lchild; //左子树
struct Node *rchild; //右子树
} TreeNode;
typedef struct { //存储输入要生成哈夫曼编码的数组
int data; //权值
char c; //字符
} Weight;
Weight Num[] = { //要编码的字符,权值放大100倍
{31, 'A'}, //0.31
{20, 'C'}, //0.2
{9, 'G'}, //0.09
{40, 'T'}, //0.4
};
//查找权值小的下标
void FindLetter(int *pInt, int *pInt1, TreeNode **pNode, int l) {
int i, data, mark, mark1;
mark = *pInt;
mark1 = 0;
data = pNode[*pInt]->data;
for (i = 0; i < l; ++i) {
if (pNode[i] == NULL) {
continue;
}
if (data > pNode[i]->data) {
data=pNode[i]->data;
mark = i; //最小权值下标
}
}
data = pNode[*pInt]->data; //从数组第一个开始比较
for (i = 0; i < l; ++i) {
if (pNode[i] == NULL || i == mark) {
continue;
}
if (data >= pNode[i]->data) {
data=pNode[i]->data;
mark1 = i; //次小权值下标
}
}
*pInt = mark;
*pInt1 = mark1;
}
//构建哈夫曼树
TreeNode *CreateHuffman(int l) {
int i, k1, k2;
k1 = k2 = 0;
TreeNode *p = NULL; //最终存储哈夫曼树的数组
TreeNode **mid = (TreeNode **) malloc(sizeof(TreeNode *) * l); //构造哈夫曼树的一个中间过渡动态数组
for (i = 0; i < l; ++i) { //初始化中间状态数组
mid[i] = (TreeNode *) malloc(sizeof(TreeNode));
mid[i]->c = Num[i].c; //字符存入动态数组
mid[i]->data = Num[i].data; //权值存入动态数组
mid[i]->lchild = NULL;
mid[i]->rchild = NULL;
}
for (i = 0; i < l - 1; ++i) { //循环合并构造树,循环字符串总数l-1次
FindLetter(&k1, &k2, mid, l); //查找最小权值的数组下标k1,以及次小的下标k2.
p = (TreeNode *) malloc(sizeof(TreeNode));
p->data = mid[k1]->data + mid[k2]->data; //合并节点
p->lchild = mid[k1];
p->rchild = mid[k2];
mid[k1] = p;
mid[k2] = NULL;
}
free(mid);
return p;
}
//构造哈夫曼编码
void PreHuffOrder(TreeNode *p, int i, char c) {
if (p != NULL) {
if (i > 0) {
code[i - 1] = c; //在父节点的基础上添加新的编码(根节点不添加)
}
PreHuffOrder(p->lchild, i + 1, '0'); //左子树编码0
PreHuffOrder(p->rchild, i + 1, '1'); //右子树编码1
if (p->lchild==NULL&&p->rchild==NULL) { //判断叶子节点
printf("%c: ", p->c); //输出字符
printf("%s\n", code); //输出当前字符编码
code[i-1]=0; //完成操作后将刚填入的编码置0
}
}
}
//主函数
int main() {
TreeNode *p;
p = CreateHuffman(N); //构造哈夫曼树
PreHuffOrder(p, 0, '0'); //输出哈夫曼编码
return 0;
}
3、相关文件:Github