1.Huffman树的基础概念
路径:从树中的一个节点到另一个节点之间的分支构成这两个节点的路径。
路径长度:路径上分支的数目。
树的路径高度:从根到每一个节点的路径之和。
节点的带权路径长度:从该节点到树根之间的路径长度与节点上权的乘积。
树的带权路径长度:树中所有叶节点的带权路径长度之和,通常记作WPL(Weighted Path Length of Tree)。
Huffman树:假设有n个权值{m1,m2,m3,,,,,mn},可以构造一棵具有n个叶子节点的二叉树,每个叶子节点带权为mi,则其中带权路径长度WPL最小的二叉树称作Huffman树。
根据定义,Huffman树是带权路径长度最小的树,假设一个二叉树有4个节点,分别是A,B,C,D,其权重分别是5,7,2,13,通过这4个节点可以构成多种二叉树。
WPL(A)=5*2 + 7*2 + 2*2 + 13*2 = 54
WPL(B)=5*1 + 7*2 + 2*3 + 13*3 = 64
WPL(C)=13*1 + 7*2 + 2*3 + 5*3 = 48
通过上面的例子,其实可以发现的规律是在构造二叉树的时候,将权值大的节点尽量放在路径高度小的位置,而将权值小的节点尽量放在路径高度大的位置,这样就可以得到最优的二叉树。
2.Huffman树的构造过程
(1)将给定的n个权值{m1,m2,m3,,,,,mn}作为n个根节点的权值并构造一棵具有n棵二叉树的森林{T1,T2,T3,,,,Tn},其中每棵二叉树都只有一个节点。
(2)在森林中选取两棵根节点权值最小的二叉树,作为左右子树并构造一棵二叉树,新二叉树的根节点权值为这两棵树根的权值之和。
(3)在森林中,将上面选择的两棵权值最小的二叉树从森林中删除,同时将上一步中新构造的二叉树加入到森林中。
(4)重复(2)(3)步骤,直到森林中只有一棵二叉树为止,这棵就是Huffman树。
3.Huffman编码
在现实中,如果要使用ASCII码来设计电文,会浪费许多的空间,因为所有的编码都是7位。因此为了节省空间,可以将电文设计成二进制前缀编码,其实就是以n种字符的出现频率作为权重,然后设计Huffman树的过程。正是因为这个原因,所以通常将二进制前缀编码称为Huffman编码。
Huffman树的数据结构
(1).我们采用数组的形式来保存Huffman树,所以在该结构父节点,左右子树节点都保存在数组对应的下标位置,因此不采用指针变量,还需要定义一个字符数指针,用来指向Huffman编码字符串。
<pre name="code" class="cpp">typedef struct HuffmanTree {
int weight ;
int parent ;
int left ;
int right ;
}HuffmanTree ;
typedef char * HuffmanCode ;
(2).编写创建Huffman树的代码
<pre name="code" class="cpp">void CreateTree(HuffmanTree *HuffTree , int n , int * w) {
int i , m = 2 * n - 1 ;
int bt1 , bt2 ;
if(n <= 1)
return ;
for(i = 1 ; 1 <= n ; ++ i) {
HuffTree[i].weight = w[i-1] ;
HuffTree[i].parent = 0 ;
HuffTree[i].left = 0 ;
HuffTree[i].right = 0 ;
}
for(;i <= m ; ++ i){
HuffTree[i].weight = 0 ;
HuffTree[i].parent = 0 ;
HuffTree[i].left = 0 ;
HuffTree[i].right = 0 ;
}
for(i = n + 1 ; i < m ; ++ i) {
SelectNode(HuffTree , i -1 , &bt1 , &bt2) ;
HuffTree[bt1].parent = i ;
HuffTree[bt2].parent = i ;
HuffTree[i].left = bt1 ;
HuffTree[i].right = bt2 ;
HuffTree[i].weight = HuffTree[bt1].weight + HuffTree[bt2].weight ;
}
}
其中参数:
HuffTree :指向HuffTree树的指针,调用函数申请内存,并得到这个指针
n :创建HuffTree树的叶节点数量
w :是一个指针,用于传入n个节点的权值
(3).编写SelectNode()函数,功能是在创建Huffman树函数CreateTree()中反复使用,该函数用于从无父节点中选出2个权值最小节点
void SelectNode(HuffmanTree * HuffTree , int n , int * bt1 , int * bt2) {
//从1-x个节点中选择parent节点为0,权重最小的两个节点
int i ;
HuffmanTree *HuffTree1 , *HuffTree2 , *t ;
HuffTree1 = NULL ;
HuffTree2 = NULL ; //初始化两个节点为空
for(i = 1 ; i <= n ; ++ i) { //循环处理1-n个节点
if(HuffTree[i].parent = 0) { //父节点为空
//找到第一个父节点为空的节点,作为HuffTree1
if(HuffTree1 == NULL) {
HuffTree1 = HuffTree + i ; //指向第i个节点
continue ; //继续循环
}
//找到第二个父节点为空的节点,作为HuffTree2
if(HuffTree2 == NULL) { //节点指针2为空
HuffTree2 = HuffTree + i ; //指向第i个节点
if(HuffTree1 ->weight > HuffTree2 ->weight) { //比较两个节点的权重,使HuffTree1指向的节点权重小
t = HuffTree2 ;
HuffTree2 = HuffTree1 ;
HuffTree1 = t ;
}
continue ;
}
if(HuffTree1 && HuffTree2) { //若HuffTree1,HuffTree2两个指针都有效
if(HuffTree[i].weight <= HuffTree1 ->weight) { //第i个节点权重小于HuffTree1指向的节点
HuffTree2 = HuffTree1 ; //
HuffTree1 = HuffTree + i ; //
}
else if(HuffTree[i].weight < HuffTree2 ->weight) { //
HuffTree2 = HuffTree + i ; //
}
}
}
}
if(HuffTree1 > HuffTree2) { //增加比较,使二叉树左侧为叶子节点
*bt2 = HuffTree1 - HuffTree ;
*bt1 = HuffTree2 - HuffTree ;
}
else {
*bt2 = HuffTree2 - HuffTree ;
*bt1 = HuffTree1 - HuffTree ;
}
void HuffmanCoding(HuffmanTree *HuffTree , int n , HuffmanCode * HuffCode) {
char * cd ;
int start , i ;
int current , parent ;
cd = (char *)malloc(n * sizeof(char)) ; //
cd[n-1] = '\0' ; //
for(i = 1 ; i <= n ; ++i) {
start = n - 1 ;
current = i ;
parent = HuffTree[current].parent ;
while(parent) {
if(current == HuffTree[parent].left)
cd[--start] = '0' ;
else
cd[--start] = '1' ;
current = parent ;
parent = HuffTree[parent].parent ;
}
HuffCode[i-1] = (char *)malloc((n - start) * sizeof(char)) ;
strcpy(HuffCode[i-1] , & cd[start]) ;
}
free(cd) ;
}
int main(void) {
int i , n = 4 , m ;
char test[] = "DBDBDABDCDADBDADBDADACDBDBD" ;
char code[100] , code1[100] ;
char alphabte[] = { 'A' , 'B' , 'C' , 'D' } ;
int w[] = {5 , 7 , 2 , 13} ;
HuffmanTree *HuffTree ;
HuffmanCode * HuffCode ;
m = 2 * n - 1 ;
HuffTree = (HuffmanTree *)malloc((m + 1) * sizeof(HuffmanTree)) ;
HuffCode = (HuffmanCode *)malloc((n * sizeof(char *))) ;
CreateTree(HuffTree , n , HuffCode) ;
HuffmanCoding(HuffTree , n , HuffCode) ;
for(i = 1 ; i <= n ; ++ i)
printf("字母:%c , 权重:%d , 编码为:%s\n" , alphabte[i - 1] , HuffTree[i].weight , HuffCode[i - 1]) ;
return 0 ;
}
</pre><pre name="code" class="cpp">
此外,还有一种传统的树形结构的方法,优点是较为直观,能够形象的模拟出构造Huffman树的过程,但缺点是较为浪费空间。
#include <stdio.h>
#include <stdlib.h>
typedef struct HuffTree {
char ch ;
int weight ;
struct HuffTree * left ;
struct HuffTree * right ;
}HuffTree ;
void Huffman(HuffTree ** forest , int num) {
int min_1 , min_2 ;
int i , j ;
HuffTree * newNode = NULL ;
for(i = 0 ; i < num -1 ; ++ i) {
//找到第一个没有父节点的二叉树
for(min_1 = 0 ; min_1 < num && forest[min_1] == NULL ; min_1 ++) ;
//找到第二个没有父节点的二叉树
for(min_2 = min_1 ++ ; min_2 < num && forest[min_2] == NULL ; min_2 ++) ;
//通过一次遍历找到权值最小和次小的两个二叉树
//如果初始情况下,min_1的权值大于min_2,那么通过第一次比较就可以交换
for(j = min_2 ; j < num ; ++ j) {
//如果当前节点已经有父节点,就跳过
if(forest[j]) {
//如果当前节点比最小的节点权值更小,就把它赋给最小,把原来的最小赋给次小
if(forest[j] ->weight < forest[min_1] -> weight) {
min_2 = min_1 ;
min_1 = j ;
}
//而如果当前节点比最小大,比次小小,就把它赋给次小
else if(forest[j] -> weight < forest[min_2] -> weight) {
min_2 = j ;
}
newNode = (HuffTree *) malloc (sizeof(HuffTree)) ;
newNode ->weight = forest[min_1] -> weight + forest[min_2] -> weight ;
newNode ->left = forest[min_1] ;
newNode ->right = forest[min_2] ;
forest[min_1] = newNode ;
forest[min_2] = NULL ;
}
}
}
}
int main(void) {
int num ;
int i ;
printf("Input the num of codes:") ;
scanf("%d" , num) ;
HuffTree ** forest = (HuffTree **) malloc (num * sizeof(HuffTree *)) ;
//initialization forest
for(i = 0 ; i < num ; ++ i) {
forest[i] = (HuffTree *) malloc (sizeof(HuffTree)) ;
printf("input the No.%d HuffTree's charactor:" , i) ;
scanf("%c" , forest[i] -> ch) ;
printf("input the No.%d HuffTree's charactor:" , i) ;
scanf("%d" , forest[i] -> weight) ;
forest[i] -> right = NULL ;
forest[i] -> left = NULL ;
}
Huffman(forest , num) ;
return 0 ;
}