赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用。
1 基本概念
① 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径。
② 路径长度:结点路径上的分支数目称为路径长度。
③ 树的路径长度:从树根到每一个结点的路径长度之和。
④ 结点的带权路径长度:从树的根结点到该结点的的路径长度与结点的权(值)的乘积。
权(值):各种开销、代价、频度等的抽象称呼。
⑤ 树的带权路径长度:树中所有叶子结点的带权路径长度之和,记做:
WPL=w1?l1+w2?l2+?+wn?ln=∑wi?li (i=1,2,?,n)
其中:n为叶子结点的个数;wi为第i个结点的权值; li为第i个结点的路径长度。
⑥ Huffman树:具有n个叶子结点(每个结点的权值为wi) 的二叉树不止一棵,但在所有的这些二叉树中,必定存在一棵WPL值最小的树,称这棵树为Huffman树(或称最优树) 。
根据n个权值 {w1, w2, ?,wn},构造成n棵二叉树的集合F={T1, T2, ?,Tn},其中每棵二叉树只有一个权值为wi的根结点,没有左、右子树;
② 在F中选取两棵根结点权值最小的树作为左、右子树构造一棵新的二叉树,且新的二叉树根结点权值为其左、右子树根结点的权值之和;
③ 在F中删除这两棵树,同时将新得到的树加入F中;
④ 重复②、③,直到F只含一颗树为止。
构造Huffman树时,为了规范,规定:
F={T1,T2, ?,Tn}中权值小的作为新构造的二叉树的左子树,权值大的作为右子树; 在取值相等时,深度小的作为新构造的二叉树的左子树,深度大的作为右子树。
例:构造权值集合为W={8, 3, 4, 6, 5, 5}的 Huffman树。
1 Huffman编码
在电报收发等数据通讯中,常需要将传送的文字转换成由二进制字符0、1组成的字符串来传输。为了使收发的速度提高,就要求电文编码要尽可能地短。
要设计长短不等的编码,须保证任意字符的编码都不是另一个字符编码的前缀,这种编码称为前缀编码。
Huffman树可以用来构造编码长度不等且译码不产生二义性的编码。
设电文中的字符集C={c1,c2, ?,ci, ?,cn},各个字符出现的次数或频度集W={w1,w2, ?,wi, ?,wn}。
Huffman编码方法
以字符集C作为叶子结点,次数或频度集W作为结点的权值来构造 Huffman树。规定Huffman树中左分支代表“0”,右分支代表“1” 。
从根结点到每个叶子结点所经历的路径分支上的“0”或“1”所组成的字符串,为该结点所对应的编码,称之为Huffman编码。
由于每个字符都是叶子结点,不可能出现在根结点到其它字符结点的路径上,所以一个字符的Huffman编码不可能是另一个字符的Huffman编码的前缀。
若字符集C={a, b, c, d, e, f}所对应的权值集合为W={8, 3, 4, 6, 5, 5},如图6-25所示,则字符a,b, c,d, e,f所对应的Huffman编码分别是:10,010,011,00 ,110,111。
(1) 数据结构设计
Huffman树中没有度为1的结点,有n个叶子结点的Huffman树共有2n-1个结点,则可存储在大小为2n-1的一维数组中。实现编码的结点结构如图6-26所示。
原因:
◆ 求编码需从叶子结点出发走一条从叶子到根的路径;
◆ 译码需从根结点出发走一条到叶子结点的路径。
结点类型定义:
#define MAX_NODE 200 /* Max_Node>2n-1 */
typedef struct
{ char elem; // 待编码字符
unsigned int Weight ; /* 权值域 */
unsinged int Parent , Lchild , Rchild ;
} HTNode, *HuffmanTree ;
typedef char **HuffmanCode;
typedef struct Node
{
char elem;
unsigned int m_weight;
}Node; // 字符及其权值
(2) Huffman树的生成
算法实现
待编码字符的输入:
cout<<"输入要编码字符个数:" ;
cin>>n;
w=(Node *)malloc(n*sizeof(Node));
cout<<"输入字符及其对应的权值:"<<endl;
for(i=0;i<n;i++)
{
cin>>c>>wei;
w[i].elem=c;
w[i].m_weight=wei;
}
从前n个结点中,选取权值比较小的两个:最小的下标为s1,次小的下标s2
void Select(HuffmanTree HT,int n,int &s1,int &s2)
{ int i; s1=s2=0;
for(i=1;i<=n;i++)
{ if((HT[i].m_weight<HT[s2].m_weight)&&(HT[i].parent==0)&&(s2!=0))
{ if(HT[i].m_weight<HT[s1].m_weight)
{ s2=s1; s1=i; }
else s2=i;
}
if((s1==0||s2==0)&&HT[i].parent==0)
{ if(s1==0) s1=i;
else if(s2==0)
{ if(HT[i].m_weight<HT[s1].m_weight)
{ s2=s1; s1=i; }
else s2=i;
} // end of else if
} // end of if
} // end of for
return;
}
新结点的构造
for(i=n+1;i<=m;++i)
{
Select((*HT),i-1, s1,s2);
(*HT)[s1].parent=i; (*HT)[s2].parent=i;
(*HT)[i].lchild=s1;
(*HT)[i].rchild=s2;
(*HT)[i].m_weight=(*HT)[s1].m_weight+(*HT)[s2].m_weight;
}
(3) Huffman编码算法
根据出现频度(权值)Weight,对叶子结点的Huffman编码有两种方式:
① 从叶子结点到根逆向处理,求得每个叶子结点对应字符的Huffman编码。
② 从根结点开始遍历整棵二叉树,求得每个叶子结点对应字符的Huffman编码。
由Huffman树的生成知,n个叶子结点的树共有2n-1个结点,叶子结点存储在数组HT中的下标值为1∽n。
① 编码是叶子结点的编码,只需对数组HT[1…n]的n个权值进行编码;
② 每个字符的编码不同,但编码的最大长度是n。
编码
(*HC)=(HuffmanCode)malloc(n*sizeof(char*));
cd = (char *)malloc(n*sizeof(char));
cd[n-1]=‘\0‘;
for(i=1;i<=n;++i)
{
start=n-1;
for(c=i,f=(*HT)[i].parent;f!=0;c=f,f=(*HT)[f].parent)
{
if((*HT)[f].lchild==(unsigned int)c)
cd[--start]=‘0‘;
else cd[--start]=‘1‘;
}
(*HC)[i]=(char *)malloc((n-start)*sizeof(char));
strcpy((*HC)[i],&cd[start]);
}
原文地址:http://blog.csdn.net/wangzi11322/article/details/45391241