1、最小冗余编码:给出现的符号编码,使用较少的位对应出现频率高的符号编码,较多的位给出现频率较低的符号编码。霍夫曼编码基于此。
2、数据结构
//霍夫曼树的节点,即data指向的内容,包含符号标记(就是数组下标)和频率
typedef struct Huffnode_
{
unsigned char symbol;
int freq;
}HuffNode;
//每个符号对应的编码表,used:符号有没有被使用;code:对应编码;szie:编码的位数
typedef struct HuffCode_{
unsigned char used;
unsigned short code;
unsigned char size;
}HuffCode;
3、创建霍夫曼树,最终生成的霍夫曼树最底层为所有符号节点
(1)先为每个符号建一棵树,这个符号作为根节点插入,并把这些单个符号入队列
(2)出队列2个最小的点 ,把他们合成一个树,把这个合成的树再加入队列,循环直到合成最后一颗树:霍夫曼树。
(3)最后把生成的霍夫曼树出口给tree指针
static int build_tree(int *freqs, BIT_TREE **tree)
{
BIT_TREE *init, *merge, *left, *right;
PQueue pqueue;
HuffNode *data;
int size, c;
*tree = NULL;
pqueue_init(&pqueue, compare_freq, destroy_tree);
for(c = 0; c <= UCHAR_MAX; c++)
{
if(freqs[c] != 0)
{
//先为每个符号建一棵树,这个符号作为根节点插入
init = (BIT_TREE *)malloc(sizeof(BIT_TREE));
bittree_init(init, free);
data = (HuffNode *)malloc(sizeof(HuffNode));
data->symbol = c;
data->freq = freqs[c];
bittree_ins_left(init, NULL, data);
//把这些单个符号入队列
//优先队列,出队列是头部最大或最小,入队列从后面入
pqueue_insert(&pqueue, init);
}
}
size = pqueue_size(&pqueue);
for(c = i;c <= size - 1; c++)
{
merge = (BIT_TREE *)malloc(sizeof(BIT_TREE));
//出队列连个最小的点 ,把他们合成一个树
//把这个合成的树再加入队列,循环直到合成最后一颗树:霍夫曼树
//最终生成的霍夫曼树最底层为所有符号节点
pqueue_extract(&pqueue, (void **)&left);
pqueue_extract(&pqueue, (void **)&right);
data = (HuffNode *)malloc(sizeof(HuffNode));
memset(data, 0, sizeof(NuffNode));
data->freq = ( (HuffNode *)bittree_data(bittree_root(left)) )->freq + ( (HuffNode *)bittree_data(bittree_root(right)) )->freq;
bittree_merge(merge, left, right, data);
pqueue_insert(&pqueue, merge);
//需要手动释放,不然每次循环都在申请空间
free(left);
free(right);
}
//最后把生成的霍夫曼树出口给tree指针
pqueue_extract(&pqueue, (void **)tree);
return 0;
}
4、建表
//table为最终生成的表,每个元素是每组符号和其对应的编码
static void build_table(BIT_TREE_NODE *node, unsigned short code, unsigned char size, HuffCode *table)
{
if(node->left != NULL)
//向下遍历,左边编码补0
build_table(node->left, code << 1, size + 1, table);
if(node->right != NULL)
//向下遍历,左边编码补0
build_table(node->right, (code << 1) | 0x0001, size + 1, table);
//符号节点都在最底层,即无左右子叶
if((node->left == NULL) && (node->right == NULL))
{
//保证编码为大端模式
code = htons(code);
//符号就是下标,8位数据以自身对应的大小为符号。没出现的值为0,used = 0
table[ ( (HuffNode *)(node->data) )->symbol ].used = 1;
table[ ( (HuffNode *)(node->data) )->symbol ].code = code;
table[ ( (HuffNode *)(node->data) )->symbol ].size = size;
}
}
5、数据压缩
压缩过程建的数和表只是过程量,不占空间。最终的压缩数据只多了头信息(原数据长度 + 每个符号出现的次数)
//original:原始数据;compressed:压缩后数据;size:原始数据的字节数
int huffman_compress(const unsigned char *original, unsigned char **compressed, int size)
{
BIT_TREE *tree;
HuffCode table[UCHAR_MAX + 1];
int freqs[UCHAR_MAX + 1], max, scale, hsize, ipos, opos, cpos, c, i;
unsigned char *comp, *temp;
//初始化操作
*compressed = NULL;
for(c = 0; c <= UCHAR_MAX; c++)
freqs[c] = 0;
//t统计符号出现的频率,没出现的保持为0
ipos = 0;
if(size > 0)
{
while(ipos < size)
{
freqs[original[ipos]]++;
ipos++;
}
}
//为压缩频率做准备,如果有符号出现次数大于符号个数UCHAR_MAX,
//则max为最多的那个元素,否则max = UCHAR_MAX(不压缩);
//压缩的目的是使每个频率数值都能用叫少的位表示,
//数据种类最多为UCHAR_MAX,那么频率的变化也不会超过UCHAR_MAX种;
max = UCHAR_MAX;
for(c = 0; c <= UCHAR_MAX; c++)
{
if(freqs[c] > max)
max = freqs[c];
}
for(c = 0; c <= UCHAR_MAX; c++)
{
scale =(int)(freqs[c] / ( (double)max / (double)UCHAR_MAX ));
//压缩后的数据为零点几,存为1
if(scale == 0 && freqs[c] != 0)
freqs[c] = 1;
else
freqs[c] = scale;
}
//建树
build_tree(freqs, &tree);
for(c = 0; c <=UCHAR_MAX; c++)
memset(&table[c], 0, sizeof(HuffCode));
//建表
build_table(tree->root, 0x0000, 0, table);
bittree_destroy(tree);
free(tree);
//建立头信息,大小为 4 + UCHAR_MAX + 1 个字节
hsize = sizeof(int) + (UCHAR_MAX + 1);
comp = (unsigned char *)malloc(hsize);
//前4个字节存带压缩数据的总字节数
memcpy(comp, &size, sizeof(int));
//后面每个字节存放每个符号出现的频率
for(c = 0; c <= UCHAR_MAX; c++)
comp[sizeof(int) + c] = (unsigned char)freqs[c];
ipos = 0;
opos = hsize * 8;
//压缩后的数据前面放头信息,之后放每个数据对应的编码
while(ipos < size)
{
c = original[ipos];
//table[c].size为编码的位数, 不能对这些为直接赋值,只能一位一位操作
for(i = 0; i < table[c].size; i++)
{
//opos每次 + 1bit,加到一次8,即多一字节循环一次
if(opos % 8 == 0)
{
temp = (unsigned char *)realloc(comp, (opos / 8) + 1);
comp =temp;
}
//从当前符号的编码中取出一位给cpos,从高位先取,如5位编码code=0b10101=0b000_10101
cpos = (sizeof(short) * 8) - table[c].size + i;
bit_set(comp, opos, bit_get((unsigned char *)&table[c].code, cpos));
opos++;
}
ipos++;
}
//返回压缩后的数据字节数
*compress = comp;
return ((opos - 1) / 8) + 1;
}
6、数据解压,并不需要建表,根据编码的0、1左右向下找到最底层树节点即找到该符号。找到的数据为字节数据可以直接赋值。而压缩时必须建表,因为霍夫曼树不能向上遍历,所以不能根据符号从树中直接得到编码。
int huffman_uncompress(const unsigned char *compressed, unsigned char **original)
{
BIT_TREE *tree;
BIT_TREE_NODE *node;
int freqs[UCHAR_MAX + 1];//0到UCHAR_MAX是UCHAR_MAX+1个数
int hsize, size, ipos, opos, state, c;
unsigned char *orig, *temp;
*original = orig = NULL;
//从压缩数据的头信息中读出数据长度和符号频率数组
hsize = sizeof(int) + (UCHAR_MAX + 1);
memcpy(&size, compressed, sizeof(int));
for(c = 0; c <= UCHAR_MAX; c++)
freqs[c] = compressed[sizeof(int) + c];
build_tree(freqs, &tree);
ipos = hsize * 8;//ipos是压缩数据中的第几bit位
opos = 0;//解压后数据的第几个字节
node = tree->root;
while(opos < size)
{
state = bit_get(compressed, ipos);
ipos++;
if(state == 0)
node = node->left;
else
node = node->right;
if((node->left == NULL) && (node->right == NULL))
{
if(opos == 0)
orig = (unsigned char *)malloc(1);
else
{
temp = (unsigned char *)realloc(orig, opos + 1);
orig = temp;
}
//此时找到的数据为字节数据可以直接赋值
orig[opos] = ((HuffNode *)bittree_data(node))->symbol;
opos++;
//回到树顶点为下次遍历准备
node = tree->root;
}
}
bittree_destroy(tree);
free(tree);
*original = orig;
return opos;
}