哈夫曼编码:
所谓编码就是对于任意给定的文本,通过查阅编码表逐一将其中的字符转译为二进制编码,这些编码依次串接起来即得到了全文的编码。
编码方案确定后,尽管编码结果必然确定,但解码过程和结果却不见得唯一。例如,字符a的编码为01,b的编码为011,c的编码为11,d的编码为101。当给定一串字符011101,结果可以编译为aca,也可以编译为bd。从而导致歧义。分析不难发现,出现歧义的原因是a的编码是b编码的前缀。
为了避免歧义的出现,在制定编码方案时,我们应该保证每一个编码节点不能是另一个编码节点的父节点。
1952年,数学家D.A.Huffman提出了根据字符在文件中出现的频率,用0/1的数字串表示各字符的最佳编码方式。
算法思想:
哈夫曼编码:根据字符的使用频率作为权值构建一棵哈夫曼树,然后利用哈夫曼树对字符进行编码。构造一棵哈夫曼树,是将所要编码的字符作为叶子节点,该字符的使用频率作为叶子节点的权值,以自底向上的方式,通过不断合并后构造出的一棵树。
算法采用的贪心策略是:在森林中不断寻找两个权值最小且没有父节点的节点,然后将其合并为一棵二叉树,直至森林中仅剩一棵树。
构造步骤:
- 获取字符及其出现的频率,将这样的数值作为树节点放入森林
- 遍历森林,找到节点权值最小且没有父节点的两个节点
- 合并2中的两个节点,权值较小的作为左孩子,另一个作为右孩子
- 将新生成的树放入森林,且转至2,继续查找,直至森林中仅剩一棵树
实现思路:
- 为了操作方便,我们声明一个节点结构体,包含父节点、左孩子、右孩子、字符、以及出现的频率等数据域,并且采用数组存储这些节点。
- 初始状态下(节点没有父节点、左孩子、右孩子、出现频率),因此我们首先对存储数组初始化,初始化标准:父节点、左孩子、右孩子均为-1,出现频率为0。
- 初始结束后,获取各个节点的数据(字符、出现频率)。通过遍历存储数组,不断从其中找到权重较小的两个节点合并。但是由于数值获取的随机性即权重较小的节点在数组中保存的位置随机,因此我们不得不每次都对整个存储数组进行遍历,此外为了避免已经合并过的节点再次合并,我们在查找过程中还需要加上一个条件-节点的父节点域为-1(即不存在父节点)。
- 合并过程:因为采用数组存储,所以每次合并后,只需要将新合成的节点放入数组末尾即可。
代码示例:
void create_huff_tree(int n){
int m1, m2, x1, x2;
for(int i = 0; i < n-1; i++){
m1 = m2 = MAX_VALUE;
x1 = x2 = 0;
for(int j = 0; j < n + i; j++){
if(huffNode[j].parent == -1){//查找父节点为空中的
if(huffNode[j].weight < m1){//最小节点
m2 = m1;
x2 = x1;
m1 = huffNode[j].weight;
x1 = j;
} else if(huffNode[j].weight < m2){//次小节点
m2 = huffNode[j].weight;
x2 = j;
}
}
}
huffNode[x1].parent = n + i;//更新找到的两个节点父节点
huffNode[x2].parent = n + i;
huffNode[n+i].weight = m1 + m2;//生成新节点,且加入森林
huffNode[n+i].left = x1;
huffNode[n+i].right = x2;
}
编码步骤:
分析:编码过程中,每个待编码字符都是树的叶子节点,并且树的节点采用数组存储。因此,我们可以遍历0-n号元素(0-n,为待编码字符节点),从叶子节点向根编码。
- 为了对应每一个待编码节点,我们声明一个结构体,包含记录二进制信息的数组以及数组的开始位置。
- 因为叶子节点的深度可能不一致,因此声明一个临时变量保存编码信息
- 遍历叶子节点到根节点中间的节点。在此规定,左子树为0,右子树为1,因此遍历过程中需要判断是左子树还是右子树。
代码示例:
void huff_coding(int n){
Code temp;
int c, p;
for(int i = 0; i < n; i++){
temp.start = n-1;
c = i;
p = huffNode[c].parent;
while(p != -1){//从叶子到根遍历
if(huffNode[p].left == c)
temp.bit[temp.start--] = 0;
else
temp.bit[temp.start--] = 1;
c = p;
p = huffNode[c].parent;
}
for(int j = temp.start+1; j < n; j++)//临时变量赋值给对应变量保存编码
huffCode[i].bit[j] = temp.bit[j];
huffCode[i].start = temp.start;
}
}
完整代码:
#include<iostream>
using namespace std;
#define MAX_VALUE 1e7
#define MAX_SIZE 100
struct Node{
int weight;
int parent;
int left, right;
char chr;
};
struct Code{
int bit[MAX_SIZE];
int start;
};
Node huffNode[MAX_SIZE];
Code huffCode[MAX_SIZE];
void create_huff_tree(int n){
int m1, m2, x1, x2;
for(int i = 0; i < n-1; i++){
m1 = m2 = MAX_VALUE;
x1 = x2 = 0;
for(int j = 0; j < n + i; j++){
if(huffNode[j].parent == -1){
if(huffNode[j].weight < m1){
m2 = m1;
x2 = x1;
m1 = huffNode[j].weight;
x1 = j;
} else if(huffNode[j].weight < m2){
m2 = huffNode[j].weight;
x2 = j;
}
}
}
huffNode[x1].parent = n + i;
huffNode[x2].parent = n + i;
huffNode[n+i].weight = m1 + m2;
huffNode[n+i].left = x1;
huffNode[n+i].right = x2;
}
}
void huff_coding(int n){
Code temp;
int c, p;
for(int i = 0; i < n; i++){
temp.start = n-1;
c = i;
p = huffNode[c].parent;
while(p != -1){
if(huffNode[p].left == c)
temp.bit[temp.start--] = 0;
else
temp.bit[temp.start--] = 1;
c = p;
p = huffNode[c].parent;
}
for(int j = temp.start+1; j < n; j++)
huffCode[i].bit[j] = temp.bit[j];
huffCode[i].start = temp.start;
}
}
int main(){
int n;
cin >> n;
for(int i = 0; i < 2*n -1; i++){//初始化
huffNode[i].weight = 0;
huffNode[i].parent = -1;
huffNode[i].left = -1;
huffNode[i].right = -1;
}
for(int i = 0; i < n; i++){//输入字符和权值
cin >> huffNode[i].chr >> huffNode[i].weight;
}
create_huff_tree(n);
huff_coding(n);
for(int i = 0; i < n; i++){//打印输出编码
for(int j = huffCode[i].start+1; j < n; j++){
cout << huffCode[i].bit[j];
}
cout << endl;
}
return 0;
}