Huffman算法学习(一)
1.基本思想
以字符的使用频率作为权构建一棵Huffman树,然后利用Huffman树对字符进行编码。构造一棵Huffman树,是将所要编码的字符作为叶子结点,该字符在文件中的使用频率作为叶子结点的权值,以自底向上的方式,通过n-1次的“合并”运算后构造出的一棵新树,核心思想是权值越大的叶子离根越近。
2.求解步骤
Huffman算法,采用贪心策略,每次从树的集合中取出没有双亲且权值最小的两棵树作为左右子树,构造一棵新树,新树根节点的权值为其左右孩子结点权值之和,将新树插入到树的集合中。
- 确定合适的数据结构。编写程序前需要考虑的情况有:
- 初始化。
- 如果T中只剩下一棵树,则Huffman树构造成功,跳到步骤(6)。否则,从集合T中取出没有双亲且权值最小的两棵树ti和tj,将它们合并成一棵新树zk,新树的左孩子为ti,右孩子为tj,zk的权值为ti和tj的权值之和。
- 从集合T中删去ti,tj,加入zk。
- 重复以上3~4步。
- 约定左分支上的编码为“0”,右分支上的编码为“1”。从叶子结点到根结点逆向求出每个字符的Huffman编码,从根节点到叶子结点路径上的字符串为该叶子结点的Huffman编码;算法结束。
3.伪代码详解
在构造Huffman树的过程中,首先给每个结点的双亲、左孩子、右孩子的初始化为-1,找出所有结点中双亲为-1、权值最小的两个结点t1 ,合并为一棵二叉树,更新信息(双亲结点的权值为t1、t2权值之和,其左孩子为权值最小的结点t1,右孩子为次小的结点t2,t1、t2的双亲为双亲结点的编号)。重复此过程,构造一棵Huffman树。
(1)数据结构
每个结点的结构包括权值、双亲、左孩子、右孩子、结点字符信息这5个域。定义为结构体形式,定义结构体HnodeType:
typedef struct
{
double weight; //权值
int parent; //双亲
int lchild; //左孩子
int rchild; //右孩子
char value; //该结点表示的字符
} HNodeType;
weight | parent | lchild | rchild | value |
---|
在编码结构体中,bit[]存放结点的编码,start记录编码的开始下标,逆向编码;存储时,start从n-1开始依次递减,从后往前存储;读取时,从start+1开始到n-1,从前往后输出,即为该字符的编码。
typedef struct
{
int bit[MAXBIT]; //存储编码的数组
int start; //编码开始下标
}HCodeType; //编码结构体
0 | 1 | 1 |
---|
其中start指针指向第四个哥,最后一个格即n-1
(2)初始化
初始化存放Huffman数组HuffNode[]中的结点
for(int i=0;i<2*n-1;i++)
{
HuffNode[i].weight=0; //权值
HuffNode[i].parent=-1; //双亲
HuffNode[i].lchild=-1; //左孩子
HuffNode[i].rchild=-1; //右孩子
}
weight | parent | lchild | rchild | value | |
---|---|---|---|---|---|
0 | 5 | -1 | -1 | -1 | a |
1 | 32 | -1 | -1 | -1 | b |
2 | 18 | -1 | -1 | -1 | c |
3 | 7 | -1 | -1 | -1 | d |
4 | 25 | -1 | -1 | -1 | e |
5 | 13 | -1 | -1 | -1 | f |
6 | 0 | -1 | -1 | -1 | |
7 | 0 | -1 | -1 | -1 | |
8 | 0 | -1 | -1 | -1 | |
9 | 0 | -1 | -1 | -1 | |
10 | 0 | -1 | -1 | -1 |
输出n个叶子结点的字符及权值:
for(int i=0;i<n;i++)
{
cout<<"Please input value and weight of leaf node"<<i+1<<endl;
cin>>HuffNode[i].value>>HuffNode[i].weight;
}
(3)循环构造Huffman树
从集合T中取出双亲为-1且权值最小的两棵树ti和tj,将它们合并成一棵树zk,新树的左孩子为ti,右孩子为tj,zk的权值为ti和tj的权值之和。
int i,j,x1,x2; //x1,x2为两个最小权值结点的序号
double m1,m2; //m1、m2为两个最小权值结点的权值
for(int i=0;i<n-1;i++)
{
m1=m2=MAXVALUE; //初始化为最大值
x1=x2=0; //找出所有结点中权值最小、无双亲结点的两个结点
for(j=0;j<n+i;j++)
{
if(Huffman[j].weight<m1&&Huffman[j].parent==-1)
{
m2=m1;
x2=x1;
m1=HuffNode[j].weight;
x1=j;
}
else if(HuffNode[j].weight<m2&&HuffNode[j].parent==-1)
{
m2=HuffNode[j].weight;
x2=j;
}
}
/*更新新树信息*/
HuffNode[x1].parent=n+i; //x1的父亲为新结点编号n+i
HuffNode[x2].parent=n+i; //x2的父亲为新结点编号n+i
HuffNode[n+i].weight=m1+m2; //新结点权值为两个最小权值之和m1+m2
HuffNode[n+i].lchild=x1; //新结点n+i的左孩子为x1
HuffNode[n+i].rchild=x2; //新结点n+i的右孩子为x2
}
(1)i=0时,j=0;j<6;找双亲为-1,权值最小的两个数:
x1=0 x2=3; //x1、x2为两个最小权值结点的序号
m1=5 m2=7; //m1、m2为两个最小权值结点的权值
HuffNode[0].parent=6; //新结点n+i的左孩子为x1
HuffNode[3].parent=6; //新结点n+i的左孩子为x2
HuffNode[6].weight=12; //新结点权值为两个最小权值之和m1+m2
HuffNode[6].lchild=0; //新结点n+i的左孩子为x1
HuffNode[6].rchild=3; //新结点n+i的右孩子为x2
数据更新后如下表所示:
weight | parent | lchild | rchild | value | |
---|---|---|---|---|---|
0 | 5 | 6 | -1 | -1 | a |
1 | 32 | -1 | -1 | -1 | b |
2 | 18 | -1 | -1 | -1 | c |
3 | 7 | 6 | -1 | -1 | d |
4 | 25 | -1 | -1 | -1 | e |
5 | 13 | -1 | -1 | -1 | f |
6 | 12 | -1 | 0 | 3 | |
7 | 0 | -1 | -1 | -1 | |
8 | 0 | -1 | -1 | -1 | |
9 | 0 | -1 | -1 | -1 | |
10 | 0 | -1 | -1 | -1 |
(2)i=1时,j=0;j<7;找双亲为-1,权值最小的两个数:
x1=6 x2=5; //x1、x2为两个最小权值结点的序号
m1=12 m2=13; //m1、m2为两个最小权值结点的权值
HuffNode[5].parent=7; //新结点n+i的左孩子为x1
HuffNode[6].parent=7; //新结点n+i的左孩子为x2
HuffNode[7].weight=25; //新结点权值为两个最小权值之和m1+m2
HuffNode[7].lchild=6; //新结点n+i的左孩子为x1
HuffNode[7].rchild=5; //新结点n+i的右孩子为x2
数据更新后如下表所示:
weight | parent | lchild | rchild | value | |
---|---|---|---|---|---|
0 | 5 | 6 | -1 | -1 | a |
1 | 32 | -1 | -1 | -1 | b |
2 | 18 | -1 | -1 | -1 | c |
3 | 7 | 6 | -1 | -1 | d |
4 | 25 | -1 | -1 | -1 | e |
5 | 13 | 7 | -1 | -1 | f |
6 | 12 | 7 | 0 | 3 | |
7 | 25 | -1 | 6 | 5 | |
8 | 0 | -1 | -1 | -1 | |
9 | 0 | -1 | -1 | -1 | |
10 | 0 | -1 | -1 | -1 |
依次类推。