1. 简介
haffman编码主要用于数据压缩,huffman树可以解决二进制编码时码长最短且无二义性。haffman树是haffman编码的基础。根据字符出现的频率,利用haffman树可以构造一种不等长的二进制编码,并且构造所得的haffman编码是一种最优前缀编码,可以使编码后的电文长度最短,且保证任何一个字符的编码都不是同一字符集中另一字符码的前缀。
2.haffman树的概念
haffman树是带权路径长度最小的二叉树。
几个基本的概念:
结点的路径长度:从根结点到该结点的路径上的边数。
树的路径的长度:树中每个叶子结点的路径长度之和。
结点的带权路径长度:结点的路径长度与结点权值的乘积。
树的带权路径长度:树的带权路径长度WPL(Weight Path Length)是树中所有叶子结点的带权路径长度之和。
3.haffman树构造方法
算法:
(1)根据权值w1,w2,w3,...,wn构造n个二叉树F={T1,T2,...,Tn},其中Ti是只含权值wi的结点
(2)从F中选两个权值最小的二叉树Ti和Tj,构造一个根结点R,R的权WR为Wi+Wj。
(3)从F中删除Ti和Tj,加入新的树根结点R到F中。
(4)重复步骤2和步骤3,直到F中只有一棵树为止。
haffman树构建示意图:
4.haffman编码及其实现
haffman树是haffman编码的基础,利用haffman树可以构造haffman编码。haffman编码的基本原理是频繁使用的数据用较短的代码代替,而较少使用的数据则用较长的代码代替,这样可以使报文中的编码数减到最小,从而达到压缩的目的。
例如给出一段报文: ABCD ABCD CBD BD B DBCB
报文中出现的字符集{A,B,C,D},其中B出现的次数最多,7次,其次是D,5次,然后依次是C和A,分别出现的次数为4和2。对4个字符进行编码,至少需要两位二进制码。首先给出不经过压缩的方法,每个字符两位二进制编码为A:00 B:01 C:10 D:11,则将原文编码所得结果如下:
00011011 00011011 100111 0111 01 11011001 编码长度为36
采用 haffman编码,则 B:0 D:10 C:110 A:111
报文编码结果: 111011010 111011010 110010 010 0 1001100 编码长度为35
利用构建的haffman树的方法可以得到报文的haffman编码,具体算法如下:
(1)统计处每个符号出现的频率,例如上例中的频率统计为{2/18,7/18,4/18,5/18}.
(2)从左到右把上述的频率按从小到大的顺序排列。
(3)每一次挑出的最小的两个值作为二叉树的两个叶结点,并将它们合并后的结点作为根结点,这两个结点不再参与比较,新的根结点参与比较。
(4)重复上一步,直到最后得到和为1 的根节点。
(5)将新形成的二叉树的左结点标0,右结点标1。把从最上面的根结点到最下面的叶子结点途中遇到的0,1序列串起来,就得到各个符号的编码
当报文中高频字符频率较高时,haffman呈现出较高的压缩比。haffman不仅应用于文本编码,而且同样应用于图像编码等领域中。haffman优点在于:
a) 对于给出的报文可以得到最短编码
b) 非同一个字符的任意两个字符A和B,不会出现A的编码时B的编码的前缀这种情况。这是因为如果设编码字符集为{C1,C2,...,Cn},那么根到任何叶子结点Ci的路径都不会是另一个编码的前缀。因而两个字符之间不需要分隔符。
由上文可知,产生haffman编码需要对原始数据扫描两遍。第一遍扫描是为了统计出原始数据中的每个值出现的频率,第二遍是建立haffman树进行编码,由于需要建立二叉树并遍历二叉树生成的编码,因此数据压缩和还原速度比较慢。
HuffmanTree.cpp
#include " HuffmanTree.h "
using namespace std;
#define maxLength 100
void main()
{
HuffmanTree * tree = NULL;
HuffmanCode code;
char data[maxLength];
char * letter;
int * weight;
int count;
cout << " 请输入一行文本数据: " << endl;
cin >> data;
cout << endl;
OutputWeight(data,strlen(data), & letter, & weight, & count);
HuffmanCodeing(tree, & code, weight, count);
cout << " 字符 出现频率 编码结果 " << endl;
for ( int i = 0 ;i < count;i ++ )
{
cout << letter[i] << " " ;
cout << weight[i] / 1000.0 << " %\t " ;
count << code ++<< endl;
// code++;
// count<<code[i+1]<<endl;
}
cout << endl;
}
/* 从结点集合中选出权值最小的两个结点,将值分别赋给s1和s2 */
void Select(HuffmanTree * tree, int count, int * s1, int * s2)
{
unsigned int temp1 = 0 ;
unsigned int temp2 = 0 ;
unsigned int temp3 = 0 ;
for ( int i = 1 ; i <= count; i ++ )
{
if (temp1 == 0 )
{
temp1 = tree[i].weight;
* s1 = i;
}
else
{
if (temp2 == 0 )
{
temp2 = tree[i].weight;
* s2 = i;
if (temp2 < temp1)
{
temp3 = temp2;
temp2 = temp1;
temp1 = temp3;
temp3 = * s2;
* s2 = * s1;
* s1 = temp3;
} // if(temp2 < temp1)
} // if(temp2 == 0)
else
{
if (tree[i].weight < temp1)
{
temp2 = temp1;
temp1 = tree[i].weight;
* s2 = * s1;
* s1 = i;
}
if (tree[i].weight > temp1 && tree[i].weight < temp2)
{
temp2 = tree -> weight;
* s2 = i;
} // if(tree[i].weight > temp1 && tree[i].weight<temp2)
} // else
} // else
}
}
/* Huffman编码函数 */
void HufffmanCoding(HuffmanTree * tree, HuffmanCode * code, int * weight, int count)
{
int i;
int s1,s2;
int totalLength;
char * cd;
unsigned int c;
unsigned int f;
int start;
if (count <= 1 ) return ;
totalLength = count * 2 - 1 ;
tree = new HuffmanTree[(totalLength + 1 ) * sizeof (HuffmanTree)];
for (i = 1 ;i <= count;i ++ )
{
tree[i].parent = 0 ;
tree[i].lChild = 0 ;
tree[i].rChild = 0 ;
tree[i].weight = ( * weight);
weight ++ ;
}
// 构造haffman树
for (i = count + 1 ;i < totalLength; ++ i)
{
Select(tree,i - 1 , & s1, & s2);
tree[s1].parent = i;
tree[s2].parent = i;
tree[i].lChild = s1;
tree[i].rChild = s2;
tree[i].weight = tree[s1].weight + tree[s2].weight;
}
// 输出haffman 编码
( * tree) = (HuffmanCode)malloc((count + 1 ) * sizeof ( char * ));
cd = new char [count * sizeof ( char )];
cd[count - 1 ] = ' \0 ' ;
for (i = 1 ;i <= count; ++ i)
{
start = count - 1 ;
for (c = i,f = tree[i].parent;f != 0 ;c = f,f = tree[f].parent)
{
if (tree[f].lChild == c)
cd[ -- start] = ' 0 ' ;
else
cd[ -- start] = ' 1 ' ;
( * tree)[i] = new char [(count - start) * sizeof ( char )];
strcpy(( * tree)[i], & cd[start]);
}
}
delete [] tree;
delete [] cd;
}
/* 在字符串中查找某个字符,如果找到,则返回其位置 */
int LookFor( char * str, char letter, int count)
{
int i;
for (i = 0 ;i < count;i ++ )
{
if (str[i] == letter)
return i;
}
return - 1 ;
}
void OutputWeight( char * data, int length, char ** letter, int ** weight, int * count)
{
int i;
char * letterArray = new char [length];
int * letterCount = new int [length];
int allCount = 0 ;
int index;
int sum = 0 ;
float persent = 0 ;
for (i = 0 ;i < length;i ++ )
{
if (i == 0 )
{
letterArray[ 0 ] = data[i];
letterCount[ 0 ] = 1 ;
allCount ++ ;
}
else
{
index = LookFor(letterArray,data[i],allCount);
if (index == - 1 )
{
letterArray[allCount] = data[i];
letterCount[allCount] = 1 ;
allCount ++ ;
}
else
{
letterCount[index] ++ ;
}
}
}
for (i = 0 ;i < allCount;i ++ )
{
sum = sum + letterCount[i];
}
* weight = new int [allCount];
* letter = new char [allCount];
for (i = 0 ;i < allCount;i ++ )
{
persent = ( float )letterCount[i] / ( float )sum;
( * weight)[i] = ( int )( 1000 * persent);
( * letter)[i] = letterArray[i];
}
* count = allCount;
delete [] letterArray;
delete [] letterCount;
}
HuffmanTree.h
#define _HUFFMANTREE_H_
/*
haffman 树结构
*/
class HuffmanTree
{
public :
unsigned int weight;
unsigned int parent;
unsigned int lChild;
unsigned int rChild;
};
typedef char * HuffmanCode;
/* 从结点集合中选出权值最小的两个结点,将值分别赋给s1和s2 */
void Select(HuffmanTree * tree, int count, int * s1, int * s2);
/* Huffman编码函数 */
void HuffmanCodeing(HuffmanTree * tree, HuffmanCode * code, int * weight, int count);
/* 在字符串中查找某个字符,如果找到,则返回其位置 */
int LookFor( char * str, char letter, int count);
void OutputWeight( char * data, int length, char ** letter, int ** weight, int * count);
#endif