这篇博客里我先是介绍了什么是哈夫曼树,然后给出了如何去构造其的算法,接着引进哈夫曼编码,最后拓展到了动态哈夫曼树。废话不多说,开始吧!
1. 什么是哈夫曼
要介绍哈夫曼树,得先了解几个概念:
- 扩充二叉树:在原来的二叉树(内结点)中出现空子树时,加上特殊的结点(外结点),使得原结点在新树上不存在叶结点,假设有n个内结点和S个外结点,E表示从根结点到每个外结点的长度之和,I表示从根结点到每个内结点长度之和,长度即是经过的边的数量,性质如下:
- 性质1:S = n + 1
- 性质2:E = I + 2*n
-
加权路长:在扩充二叉树的基础上,若每个外结点都对应一个值,则假设lj是从根结点到某个外结点的路长,而wj是该外结点对应的值,则∑wj*lj便是加权路长。
-
哈夫曼树:对于给定的实数w1,w2,...,wm,构造一棵扩充二叉树,使其的加权路长最小,这棵树就是哈夫曼树。
2. 哈夫曼树的构造算法:
- 由给定的n个权值{w0,w1,...,wn-1},构造具有n棵扩充二叉树的森林F={T0,T1,...,Tn-1},其中每棵扩充二叉树Ti只有一个带权值wi的根结点,其左右子树均为空
- 在F中选取两棵根结点的权值最小的扩充二叉树,作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值是它们的和
- 在F中删除这两棵树,加入新树
- 重复2,3步,直到只剩一棵树
3. 哈夫曼编码
讲道理,假设给你一篇文档,里面全是a,b,c,d,e,你统计出来它们的出现频率,如下:
字符 | 概论 |
---|---|
a | 0.12 |
b | 0.4 |
c | 0.15 |
d | 0.08 |
e | 0.25 |
字母是以ASCII码存储的,一个字符8位,在知道概率的情况下,这种存储方式很浪费硬盘空间,设计一个编码方式,让它们在最小空间内存储,且具有前缀性(任何一个字符的编码不会是别的字符的编码前缀),这时候哈夫曼编码应运而生。
只需要将字符概率看成权值即可,然后构造哈夫曼树,在左子树的边上标记'0',右标记'1',则每个字符的路径上标记相连即编码。
简单代码实现:
//假设C是一个n个字符的集合,其中每个字符c属于C且c.freq给出它们的概率,Q是以freq比较的最小优先队列
HUFFMAN(C)
{
n = |C|;
Q = C;
for i = 1 to n-1
allocate a new node z
z.left = x = EXTRACT_MIN(Q);
z.right = y = EXTRACT_MIN(Q);
z.freq = x.freq + y.freq;
INSERT(Q,z);
}
4. 动态哈夫曼
静态哈夫曼编码最大的缺点是它需要对原始数据进行两遍扫描:第一遍统计字符频率,第二遍根据频率得到的哈夫曼树对原始数据进行编码。而动态哈夫曼树对t+1个字符的编码是根据前面t个字符而来,不需要重新进行扫描。
-
一些定义
- 原始数据:需要被压缩的数据
- 压缩数据:被压缩过的数据
- n:字母表的长度
- aj:字母表的第j个字符
- t:已经处理的原始数据中字符的总个数
- k:已经处理数据中各不相同字符的个数
-
构造规则:
- 初始化,一个根节点和左分支,权值均为空
- 对每个结点都分配了一个序列号,序列号越大,该结点权值越大
- 每读进一个字符,若字符未出现,则在空叶结点右分支加入权值为1新结点,左分支加入权值为0的叶子结点,调整后,更新各结点权值;若已经出现过,调整后更新权值
- 调整方法:先以对应的叶结点为当前结点,重复地与具有相同权值的序号最大的结点进行交换,并且使后者的父亲结点作为新的当前结点,直到遇到根结点为止
例如,已经压缩完32个字符
接着压缩一个'b',先要调整:
接着更新结点:
- 不多说,直接放代码:
#include <iostream>
#include <fstream>
#include <vector>
using namespace std;
#define MAX_ORDER 512
struct Node
{
char data;
int weight;
int order;
Node* lchild;
Node* rchild;
Node* parent;
};
Node * root = new Node; // 定义哈夫曼树的根节点
vector<Node *> Weight; //存储同权重中最大序号的节点
vector<Node *> Leaf; //存储所有叶子节点
//初始化root和weight
void init()
{
root->order = 512;
root->weight = 0;
root->data = '0';
root->lchild = NULL;
root->rchild = NULL;
root->parent = NULL;
}
//参数化初始化nyt节点
void init_nyt(Node * nyt)
{
nyt->weight = 0;
nyt->order = 0;
nyt->data = '0';
nyt->lchild = NULL;
nyt->rchild = NULL;
nyt->parent = NULL;
}
//根到叶节点路径上所有节点权重加一
void increase(Node * leaf)
{
Node * tmp = leaf;
while(tmp != root)
{
tmp->weight += 1;
tmp = tmp->parent;
}
root->weight += 1;
}
//更新Weight
void change_weight(Node * present)
{
Node * tmp = present;
bool flag;
if(!Weight.size())
Weight.push_back(present);
else
{
while(tmp != root)
{
flag = false;
for(int num=static_cast<int>(Weight.size()),i=0;i < num;i++)
{
if(Weight[i]->order == tmp->order)
flag = true;
}
if(!flag)
Weight.push_back(tmp);
tmp = tmp->parent;
}
}
}
//返回同权重最大节点
Node * Max(int wei)
{
Node * Max;
int i = 0;
int num= static_cast<int>(Weight.size());
for(;i < num;i++)
{
if(Weight[i]->weight == wei)
{
Max = Weight[i];
break;
}
}
for(;i < num;i++)
{
if(Weight[i]->weight == wei && Weight[i]->order > Max->order)
Max = Weight[i];
}
return Max;
}
//交换节点以便维持哈夫曼树性质
void exchange(Node * present)
{
Node * tmp = present;
Node * max;
Node * p = NULL;
Node * p1, *p2;
int t;
if(present == root || present->weight == 0)
return;
else
{
while(tmp && tmp != root)
{
max = Max(tmp->weight);
if(max != tmp->parent && max->order > tmp->order)
{
p1 = max->parent;
p2 = tmp->parent;
p = p1;
t = tmp->order;
tmp->order = max->order;
max->order = t;
tmp->parent = p1;
max->parent = p2;
if(p1->lchild == max)
p1->lchild = tmp;
else
p1->rchild = tmp;
if(p2->lchild == tmp)
p2->lchild = max;
else
p2->rchild = max;
}
else
p = tmp->parent;
tmp = p;
}
}
}
//编码
void encode(ofstream & out)
{
Node * tmp;
Node * par;
vector<int> code;
int num1 = static_cast<int>(Leaf.size());
int num2;
for(int i=0;i<num1;i++)
{
code.clear();
out << Leaf[i]->data << ": ";
tmp = Leaf[i];
while(tmp != root)
{
par = tmp->parent;
if(par->lchild == tmp)
code.push_back(0);
else
code.push_back(1);
tmp = par;
}
num2 = static_cast<int>(code.size());
for(int j=0;j<num2;j++)
out << code[num2-1-j] << " ";
out << endl;
}
}
//添加信息,构建动态哈夫曼树
void Add(ifstream & in,ofstream & out)
{
string str;
in >> str;
Node * p;
Node * q; //记录当前的NYT节点
Node * nyt; //即NYT节点
Node * present; //记录当前节点
int i = 0;
bool flag;
while(str[i] != ' ')
{
flag = false;
int j = 0;
for(int num=static_cast<int>(Leaf.size());j < num;j++)
{
if(Leaf[j]->data == str[i])
{
flag = true;
break;
}
}
if(!flag)
{
if(!root->weight)
q = root;
p = new Node;
nyt = new Node;
init_nyt(nyt);
p->parent = nyt->parent = q;
p->weight = 0;
p->data = str[i];
p->order = q->order -1;
nyt->order = q->order -2;
q->lchild = nyt;
q->rchild = p;
present = p;
Leaf.push_back(p);
q = nyt;
}
else
present = Leaf[j];
exchange(present);
increase(present);
change_weight(present);
encode(out);
out << endl;
i ++;
}
}
int main()
{
ifstream in("data.in");
ofstream out("data.out");
init();
Add(in,out);
in.close();
out.close();
return 0;
}