Huffman(哈夫曼)编码的C语言实现
本文将给出C语言的Huffman编码的原理,示例及C语言仿真结果,代码。
一、Huffman编码原理及举例
Huffman编码是一种信源编码,其编码目的在于以最高的编码效率利用信道容量。
例如,假定消息有五种字符序列构成,各字符出现的概率是给定的,设为a,b,c,d,e。出现概率为0.12,0.40,0.15,0.08,0.25。下面给出两种编码(映射):
字符 | 符号概率 | 编码方式1 | 编码方式2 |
---|---|---|---|
a | 0.12 | 000 | 000 |
b | 0.40 | 001 | 11 |
c | 0.15 | 010 | 01 |
d | 0.08 | 011 | 001 |
e | 0.25 | 100 | 10 |
编码方式1中任意3位二进制数字串都不是另一个3位二进制数字串的前缀,故其有前缀性毋庸置疑。在译码时,每次取3位二进制数字串,每3位译码为1个字符。
编码方式2其实也具有前缀性。但由于其编码的比特长度不同,难以看出其是否具有前缀性。我们不妨用二叉树来表示其编码(前缀编码均可以用二叉树表示):
由此,可以将前缀编码看作二叉树中的路径。每个结点的左分支附0,右分支附1。将字符作为叶结点的标号。从根结点到叶结点的路径上遇到的0或1构成的序列就是对应叶结点字符的编码。
对于编码方式1,所有字符编码长度为3,则其平均编码长度为3。但编码方式2的平均编码长度为2.2。显然编码方式2的效率更高。
采用Huffman算法可以得到最优前缀编码。
首先,从给定的字符集里选取两个出现概率最小的两个字符,以之前的例子为字符a,d。构造一个父结点,符号设为x,其对应概率为a,d符号概率之和,他的子结点分别为a,d。然后对其余结点和新结点组成的字符集和概率集按同样方式递归得到前缀编码二叉树。遍历二叉树即可得到最优前缀编码。
其构造的顺序如下:
通过以上方式,可以得到最优的编码方式:
字符 | 符号概率 | Huffman编码 |
---|---|---|
a | 0.12 | 1111 |
b | 0.40 | 0 |
c | 0.15 | 110 |
d | 0.08 | 1110 |
e | 0.25 | 10 |
经过计算可知,其平均编码长度为2.15。
二、Huffman编码的C语言实现
符号分别为A~P,共16个符号,其出现概率如下:
符号 | 概率 |
---|---|
A | 0.06 |
B | 0.12 |
C | 0.15 |
D | 0.05 |
E | 0.06 |
F | 0.02 |
G | 0.07 |
H | 0.03 |
I | 0.13 |
J | 0.09 |
K | 0.07 |
L | 0.06 |
M | 0.02 |
N | 0.02 |
O | 0.01 |
P | 0.04 |
1、初始化
首先输入初始信息并设置结点树,这里不使用指针变量,而是以索引为地址。
struct Huffman
{
double weight;
int lchild;
int rchild;
int parent;
}; //Huffman tree结构体定义
结构体表示一个二叉树结点,设置的结点数量应该为node_num个,直接定义为H。
首先对二叉树初始化。对二叉树的大小(结点数量)应为符号数的2倍减1。为修改方便,对结点数,符号数,概率空间作宏定义:
#define symbol_num 16
#define node_num 2 * (symbol_num) - 1
double symbol_P[symbol_num] = {
0.06, 0.12, 0.15, 0.05, 0.06, 0.02, \
0.07, 0.03, 0.13, 0.09, 0.07, 0.06, 0.02, 0.02, 0.01, 0.04};
初始化前symbol_node(16)个结点的权值weight为对应的概率,为后续排序方便,设置其余结点权值为1。其余子结点,父结点编号参照数据结构设置为 -1。为后续提取符号序号,构造list数组存储排序后的序号。为避免破坏原概率空间,可以设置新的初始概率空间以供更改。
for (int i = 0; i < node_num; i++)
{
if (i < symbol_num)
symbol_Ptemp[i] = symbol_P[i];
else
symbol_Ptemp[i] = 1;
}
for (int i = 0; i < node_num; i++)
{
list[i] = i;
}
for (int i = 0; i < node_num; i++)
{
H[i].parent = -1;
H[i].lchild = -1