1、概念
哈夫曼树又称最优树,是一类带权路径长短最短的树。
“带权路径长度最短”是在“度相同”的树中比较而得的结果,因此有最优二叉树、最优三叉树之称等等。
路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径。
结点的路径长度:两结点间路径上的分支数。
树的路径长度:从树根到每一个结点的路径长度之和。
结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树。
权(weight):将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积。
树的带权路径长度:树中所有叶子结点的带权路径长度之和。
满二叉树不一定是哈夫曼树
哈夫曼树中权越大的叶子离根越近
具有相同带权结点的哈夫曼树不惟一
2、构造哈夫曼树
贪心算法:构造哈夫曼树时首先选择权值小的叶子结点
哈夫曼树的结点的度数为0或2,没有度为1的结点。
包含n个叶子结点的哈夫曼树中共有2n-1个结点。
1、在哈夫曼算法中,初始时有n棵二叉树,要经过n-1次合并最终形成哈夫曼树。
2、经过n-1次合并产生n-1个新结点,且这n-1个新结点都是具有两个孩子的分支结点。
3、可见:哈夫曼树中共有n+n-1 = 2n-1个结点,且其所有的分支结点的度均不为1。
3、哈夫曼树创建实现
1.初始化HT [1....2n-1]: lch=rch=parent=0;
2.输入初始n个叶子结点:置HT[1...…n]的weight值;
3.进行以下n-1次合并,依次产生n-1个结点HT[i], i=n+1.....2n-1:
a)在HT[1..i-1]中选两个未被选过(从parent == 0的结点中选)的weight最小的两个结点HT[s1]和HT[s2], s1、s2为两个最小结点下标;
b)修改HT[s1]和HT[s2]的parent值: HT[s1] .parent=i; HT[s2] .parent=i;
c)修改新产生的HT[i]
HT[i].weight=HT[s1].weight + HT[s2].weight;
HT[i]. lch=s1; HT[i]. rch=s2;
typedef struct
{
int weight;
int parent, lch, rch;
} HTNode, *HuffmanTree;
void CreatHuffmanTree(HuffmanTree HT, int n)
{ // 构造哈夫曼树——哈夫曼算法
if (n <= 1)
return;
m = 2 * n - 1; // 数组共2n-1个元素
HT = new HTNode[m + 1]; // Q号单元未用,HT[m]表示根结点
for (i = 1; i <= m; ++i)
{ // 将2n-1个元素的lch、rch、parent置为0
HT[i].lch = 0;
HT[i].rch = 0;
HT[i].parent = 0;
}
for (i = 1; i <= n; ++i)
cin >> HT[i].weight; // 输入前n个元素的weight值
//初始化结束,下面开始建立哈夫曼树
for (i = n + 1; i <= m; i++)
{ // 合并产生n-1个结点——构造Huffman树
Select(HT, i - 1, s1, s2); // 在HT[k](1≤k≤i-1)中选择两个其双亲域为0,
// 且权值最小的结点,并返回它们在HT中的序号s1和s2
HT[s1].parent = i;
HT[s2].parent = i; // 表示从F中删除s1,s2
HT[i].lch = s1;
HT[i].rch = s2; // s1,s2分别作为i的左右孩子
HT[i].weight = HT[s1].weight + HT[s2].weight; // i的权值为左右孩子权值之和}
}
}
4、哈夫曼编码
在通讯领域,经常需要将需要传送的文字转换成由二进制字符组成的字符串。在实际应用中,由于总是希望被传送的内容总长尽可能的短,如果对每个字符设计长度不等的编码,且让内容中出现次数较多的字符采用尽可能短的编码,则整个内容的总长便可以减少。另外,需要保证任何一个字符的编码都不是另一个字符的编码前缀,这种编码成为前缀编码。
而哈夫曼编码就是一种二进制前缀编码,是最优前缀码
哈夫曼编码实现:
1、统计字符集中每个字符在电文中出现的平均概率。概率越大,要求编码越短。
2、利用哈夫曼树的特点:权越大的叶子离根越近;将每个字符的概率值作为权值,构造哈夫曼树。则概率越大的结点,路径越短。
3、在哈夫曼树的每个分支上标上0或1:结点的左分支标0,右分支标1。把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符的编码。
void CreatHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n)
{ // 从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
HC = new char *[n + 1];
// 分配n个字符编码的头指针矢量
cd = new char[n];
// 分配临时存放编码的动态数组空间
cd[n - 1] = '\0';
// 编码结束符
for (i = 1; i <= n; ++i)
{
// 逐个字符求哈夫曼编码
start = n - 1;
c = i;
f = HT[i].parent;
while (f != O)
{
// 从叶子结点开始向上回溯,直到根结点
-- start;
// 回溯一次start向前指一个位置
if (HT[f].lchild = = c)
cd[start] = 'O'; //结点c是f的左孩子,则生成代码0
else cd[start] = '1';
// 结点c是f的右孩子,则生成代码1
c = f;
f = HT[f].parent;
// 继续向上回溯
}
// 求出第i个字符的编码
HC[i] = new char[n - start];
// 为第i个字符串编码分配空间
strcpy(HC[i], &cd[start]); // 将求得的编码从临时空间cd复制到HC的当前行中}
delete cd;
// 释放临时空间
}
}// CreatHuffanCode