赫夫曼树,又称最优树,是一类带权路径长度最短树。
从树中一个节点到另一个节点之间的分支构成这两个节点之间的路径,路径上的分支数目称为路径长度。树的路径长度指的是从树根到树中其他每个节点的路径长度之和。节点的带权路径长度是指从树的根节点到该节点之间的路径长度与该节点上所带权值的乘积。树的带权路径长度定义为树中所有叶子节点的带权路径长度之和。
1、构造赫夫曼树
(1)根据给定的n个权值{ w1, w2, w3, .........,wn},构成 n 棵二叉树的集合,其中每颗二叉树 Ti 中只有一个带权为 wi 的根节点,其左右子树均为空。
(2)在 F 中选取两棵根节点的权值最小的树作为左右子树,构造一颗新的二叉树,且置新的根节点的权值为其左右子树根节点的权值之和。
(3)在 F 中删除这两棵树,同时将新得到的二叉树加入到 F 中。
重复(2)(3),直到 F 中只剩一棵树为止,这棵树便是所求的赫夫曼树。
注: 由于赫夫曼树没有度为 1 的结点,则一棵含有 n 个叶子节点的赫夫曼树共有 2n-1 个节点,可以储存在大小为 2n-1 的一维数组里面。
构造赫夫曼树的代码如下:
typedef struct
{
int weight;
int lchild,rchild;
}HTNode;
typedef struct
{
HTNode *HTree;//动态分配数组存储树的结点
int root;//根节点的位置
}HuffmanTree;
void CreateHuffmanTree(HuffmanTree &HT, int *w, int n)
{//w存放 n 个权值(均大于0),构造赫夫曼树HT.
if(n<=1) return 0;
m=2*n-1;
HT.HTree=new HTNode[m]; //为赫夫曼树分配一组顺序空间
for(p=HT.HTree, i=0; i<n; ++i,++p,++w) *p={*w, -1, -1}//n 个带权结点形成初始化的森林,每个左右孩子为空
for( ; i<m; i++) *p={0, -1, -1};//对尚未使用的结点赋初值
for(i=n; i<m; i++)
{//构建赫夫曼树
Select(HT.HTree, i-1, s1, s2);//在 HT.HTree[1...i-1]中找到两个权值最小的结点
//其序号是 s1和 s2
HT.HTree[i].lchild=s1; HT.HTree[i].rchild=s2;
HT.HTree[i].weight=HT.HTree[s1].weight + HT.HTree[s2].weight;//取左右树根节点权值之和
}
HT.root=m-1;
}
假设有五个权值为 2 ,3 , 6, 7 ,5的结点,则有
HT | weight | lchild | rchild |
---|---|---|---|
0 | 2 | -1 | -1 |
1 | 3 | -1 | -1 |
2 | 6 | -1 | -1 |
3 | 7 | -1 | -1 |
4 | 5 | -1 | -1 |
5 | 0 | -1 | -1 |
6 | 0 | -1 | -1 |
7 | 0 | -1 | -1 |
8 | 0 | -1 | -1 |
最终得到的赫夫曼树是:
HT | weight | lchild | rchild |
---|---|---|---|
0 | 2 | -1 | -1 |
1 | 3 | -1 | -1 |
2 | 6 | -1 | -1 |
3 | 7 | -1 | -1 |
4 | 5 | -1 | -1 |
5 | 5 | 0 | 1 |
6 | 10 | 4 | 5 |
7 | 13 | 2 | 3 |
8 | 23 | 7 | 6 |
2、赫夫曼树编码
从根节点出发,对赫夫曼树进行先序遍历,并在遍历的过程中“以栈记下所经的路径(向左记 0, 向右记 1)”,则从根到每个叶子节点的路径即为各个对应字符的编码。
typedef char **HuffmanCode;//动态分配数组空间存储赫夫曼树编码
void HuffmanCoding(HuffmanTree HT, HuffmanCode &HC, int n)
{//先序遍历赫夫曼树 HT,求树上 n 个叶子节点的编码存入 HC中
Stack S;//附设栈存储路径
HC=new (char *)[n];二维数组存储编码
InitStack(S);//初始化栈空间
Coding(HT, HT.root, S);
}
void Coding(HuffmanTree T, int i, Stack &S)
{
if(T)
{
if( (T.HTree[i].lchild==-1) && (T.HTree[i].rchild==-1))
{//到达叶子节点
HC[i]=new char[StackLength(S)];
StackCopytoArray(S, HC[i]);//从栈底到栈顶将栈中的字符复制到 HC[i] 中
}
else
{
Push(S, '0');
Coding(T, T.HTree[i].lchild, S);//遍历左子树
Pop(S, e);//回溯一个节点
Push(S, '1')
Coding(T, T.HTree[i].rchild, S);//遍历右子树
Pop(S, e);//回溯一个节点
}
}
}