1. 基本概念
① 结点路径:从树中一个结点到另一个结点的之间 的分支构成这两个结点之间的路径。
② 路径长度:结点路径上的分支数目称为路径长度。
③ 树的路径长度:从树根到每一个结点的路径长度之和。
④ 结点的带权路径长度:从该结点的到树的根结点 之间的路径长度与结点的权(值)的乘积。
⑤ 树的带权路径长度:树中所有叶子结点的带权路径长度之和,记做: WPL=w1 × l1+w2 × l2+ ⋯ +wn × ln=∑wi×li (i=1,2,⋯,n) 其中:n为叶子结点的个数;wi为第i个结点的权 值; li为第i个结点的路径长度。
⑥ Huffman树:具有n个叶子结点(每个结点的权值为wi) 的二叉树不止一棵,但在所有的这些二叉树中,必定存在一棵具有最小带权路径长度的二叉树,称这棵树为 Huffman树(或称最优树) 。
2.Huffman树的构造
构造哈夫曼树的原则:
- 权值越大的叶结点越靠近根结点;
- 权值越小的叶结点越远离根结点。
构造哈夫曼树的过程:
(1)给定的n个权值{W1,W2,…,Wn }构造n棵只有一个叶节点的二叉树, 从而得到一个二叉树的集合F={T1,T2,…,Tn }。
(2)在F中选取根节点的权值最小和次小的两棵二叉树作为左、右子树 构造一棵新的二叉树,这棵新的二叉树根节点的权值为其左、右子树根节点权值之和。
(3)在集合F中删除作为左、右子树的两棵二叉树,并将新建立的二叉 树加入到集合F中。
(4)重复(2)、(3)两步,当F中只剩下一棵二叉树时,这棵二叉树便是所要建立的哈夫曼树。
过程图如下:
哈夫曼树的特点:n1=0,所以:
n = n0+n1+n2
= n0+n2
= 2 n0 -1
3. 哈夫曼编码及其算法
3.1 Huffman编码
①前缀编码:要设计长短不等的编码,还必须保证任意字符的编码都不是另一个字符编码的前缀,这种编码称为前缀编码。
②Huffman编码方法:规定哈夫曼树中的左分支为0,右分支为1,则从根节点到每个叶节点所经过的分支对应的0和1组成的序列便为该节点对应字符的编码。这样的编码称为哈夫曼编码。
③哈夫曼编码特点:权值越大的字符编码越短,反之越长。
3.2 Huffman编码算法实现
①Huffman树和Huffman编码的存储实现
typedef struct{
unsigned int weight;
unsigned int parent,lchild,rchild;
}HTNode, * HuffmanTree; //动态分配数组存储Huffman树
typedef char ** HuffmanCode; //动态分配数组存储Huffman编码表
结点结构示意图:
weight | parent | lchild | rchild |
其中: weight:权值域; parent:双亲结点下标 ; lchild,rchild:分别标识左、右子树的下标。
② 算法实现
void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n)
// w 存放 n 个字符的权值(均 > 0),构造哈夫曼树 HT,并求出 n 个字符的哈夫曼编码 HC.
if ( n <= 1) return ;
m = 2 * n - 1;
HT = (HuffmanTree) malloc ((m + 1) * sizeof (HTNode)); //0号单元未用
for(p = HT,i = 1; i <= n; ++i,++p,++w)
*p = { *w,0,0,0 };
for( ; i <= m; ++i)
*p = { 0,0,0,0 };
for( i = n+1; i <= m; ++i){ //建哈夫曼树
//在HT[i..i-1] 选择parent 为 0 且weight 最小的两个结点,其序号分别为 s1 和 s2.
Select( HT, i-1, s1, s2);
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
//从叶子到根逆向求每个字符的哈夫曼编码
HC = (HuffmanCode) malloc ((n + 1) * sizeof (char *)); //分配 n 个字符编码的头指针向量
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[i].lchild == c)
cd[--start] = "0";
else
cd[--start] = "1";
HC[i] = (char *) malloc ((n -start) * sizeof (char)); //为第 i 个字符编码分配空间
strcpy(HC[i], &cd[start]); //从 cd 复制编码到 HC
}
free(cd);
}