引言
编码是计算机技术中的一个重要问题,在计算机中各种类型的数据都是以二进制码的形式存放在计算机中的,常见的编码形式有ASCII码。ASCII码是一种等长码,即使用7位或8位二进制数来表示128或256种可能的字符。一篇一万个的字符的文章如果采用ASCII码这种等长码来进行编码,就要要占用一万个字节,但是,文章中每个字符出现的频率可能是不一样的,如果采用一种不等长编码,将出现频率高的字符使用相对较少的位数来进行编码,那么将大大的节省存储空间。而这,就是哈夫曼树和哈夫曼编码的价值所在。
1.哈夫曼树
1.1什么是哈夫曼树
在学习哈夫曼树之前,我们先给出以下定义:
带权路径长度(WPL):设二叉树有n个叶子结点,每个叶子节点带有权值 w k w_k wk,从根结点到每个叶子结点的路径长度为 l k l_k lk,则每个叶子结点的带权路径长度之和就是:WPL= ∑ i = 1 n w k l k \sum_{i=1}^{n} w_kl_k ∑i=1nwklk。
这里的带权路径长度就可以理解为引言里面提到的字符出现的频率。借此,可以给出哈夫曼树的定义如下:
哈夫曼树:WPL最小的树。
1.2哈夫曼树的构造方法
将一组给定的数据构造为一颗哈夫曼树,可以将每个数据看做是一个结点,每次从这些结点中选择最小的两个合并成一个新的结点,新结点的权值就是这两个结点权值的和,之后将这个新的结点放到给定的结点中去。之后不断重复这个行为就可以构造出一颗哈法曼树。
所以,构造一颗哈夫曼树的核心问题就是:在一组数据中找到最小的两个。而利用最小堆可以很方面的解决这个问题。
typedef struct TreeNode *HuffmanTree;
struct TreeNode {
int Weight;
HuffmanTree Left, Right;
};
typedef struct HeapStruct *MinHeap;
struct HeapStruct {
HuffmanTree *Elements;
int Size;//堆的当前容量
int Capacity;//堆的最大容量
};
int DeleteMinHeap(MinHeap H) {
int Parent, Child;
HuffmanTree Item, MinItem;;
MinItem = H->Elements[H->Size--];
Item = H->Elements[1];
for (Parent = 1; Parent * 2 < H->Size; Parent = Child) {
Child = Parent * 2;
if (!Child == H->Size && H->Elements[Child]->Weight >= H->Elements[Child + 1]->Weight)
Child++;
if (H->Elements[Child]->Weight < Item->Weight)
H->Elements[Parent] = H->Elements[Child];
else break;
}
H->Elements[Parent] = Item;
return MinItem;
}
void Insert(HuffmanTree X,MinHeap H) {
int i;
i = ++H->Size;
for (; H->Elements[i / 2] > X->Weight; i /= 2)
H->Elements[i] = H->Elements[i / 2];
H->Elements[i] = X;
}
//哈夫曼树的构造
HuffmanTree Huffman(MinHeap H) {
HuffmanTree T;
for (int i = 1; i < H->Size; i++) {
T = (HuffmanTree)malloc(sizeof(struct HeapStruct));
T->Left = DeleteMinHeap(H);
T->Right = DeleteMinHeap(H);
T->Weight = T->Right->Weight + T->Left->Weight;
Insert(T, H);
}
T = DeleteMinHeap(H);
}
2.哈夫曼编码
有了哈夫曼树之后,我们如何利用哈夫曼树来进行哈弗曼编码,从而实现节省存储空间的目的呢?
为了避免不等长编码出现二义性,引入前缀码的概念:
前缀码(perfix code):任何字符的编码都不是另一字符编码的前缀。
利用二叉树可以避免二义性的出现:
(1)用左右分支代表0、1
(2)字符放在叶节点上。
如果我们用于编码的树是哈夫曼树的话,就可以实现没有二义性的不等长编码,即为哈夫曼码,由此可节省存储空间的占用。