一、哈夫曼树的构造过程
(1)根据给定的n个权值{,,...,},构造n棵只有根节点的二叉树,这n棵二叉树构成森林F
(2)在森林F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和
(3)在森林F中删除这两棵树,同时将新得到的二叉树加入F中
(4)重复(2)和(3),直到F只含一棵树为止,哈夫曼树构造完成
eg:已知w={5,29,7,8,14,23,3,11},试构造一棵哈夫曼树
思路如下图所示:
二、哈夫曼算法的实现
1、哈夫曼树的存储结构
由于在哈夫曼树中只有度为0和度为2的结点,则已知一棵哈夫曼树有n个叶子结点,则该哈夫曼树一共有2n-1个结点(=+1),可以存储在一个大小为2n-1的一维数组中,每个结点要包含其双亲的下标,左右孩子的下标以及自身的权值,则存储结构如图所示:
typedef struct{
int weight; //结点的权值
int parent; //结点的双亲的下标
int lchild; //结点的左孩子的下标
int rchild; //结点的右孩子的下标
}HTNode,*HuffmanTree;
哈夫曼树的各结点存储在数组中,为了方便,数组的0号单元不使用,从1号单元开始使用,所以数组的大小为2n。将叶子结点集中存储在前面部分的n个位置,而后面的n-1个位置存储其余非叶子结点的信息
2、构造哈夫曼树
操作步骤:
1、初始化:
(1)叶子结点的个数为n,则向系统动态申请2n个存储空间(0号结点的位置不使用)
(2)设置循环变量 i 的值从1到n,输入该结点的权值,并且将该结点的parent域置为 0,同时将该结点的左孩子和右孩子均置为0。
(3)设置循环变量 i 的值从 n+1到m(m=2n-1,表示总的结点个数),将该结点的权值置为 0,并且将该结点的parent域置为 0,同时将该结点的左孩子和右孩子均置为0。
2、创建树:
(1)设置循环变量 i 的值从n+1到m(m=2n-1,表示总的结点个数),每次挑选在森林中挑选两个权值最小并且还未使用过的结点,将这两个结点的下标分别赋值给两个变量s1和s2
(2)将下标为s1和s2的结点的双亲修改为i
(3)将下标为i的结点的左孩子该为s1,右孩子改为s2
(4)将下标为i的结点的权值改为下标为s1的结点的权值加上下标为s2的结点的权值
算法描述:
void CreatHuffmanTree(HuffmanTree &HT,int n)
{
if(n<=1) return;
int m=2*n-1;
HT=new HTNode[m+1]; //1(1)
for(int i=1;i<=n;i++){
cin>>HT[i].weight;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
} //1(2)
for(int i=n+1;i<=m;i++){
HT[i].weight=0;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
} //1(3)
/*初始化完成*/
for(int i=n+1;i<=m;i++){
int s1,s2;
Locate(HT,i-1,s1,s2);
HT[i].lchild=s1;HT[i].rchild=s2; //2(2)
HT[i].weight=HT[s1].weight+HT[s2].weight; //2(3)
HT[s1].parent=i;HT[s2].parent=i; //2(1)
}
}
对于上述代码的Locate函数的讲解如下:
操作步骤:
1、初始化s1的值为1,使用while循环,找到第一个双亲为0的结点(双亲为0代表该结点未被使用过),改下标此刻保存在s1中
2、设置循环变量 i 的值从s1到n,如果下标为 i 的结点的权值小于下标为 s1 的结点的权值,并且下标为 i 的结点未被使用过(双亲为0),那么就更新s1的值为 i
3、初始化s2的值为1,使用while循环,当下标为s2的结点的双亲为0,并且s2的值不等于s1的值,退出循环,此时找到了第一个下标与s1不相等且双亲为0的结点
4、设置循环变量 i 的值从s2到n,如果下标为 i 的结点的权值小于下标为 s2 的结点的权值,并且下标为 i 的结点未被使用过(双亲为0),同时 i 的值与s1的不相等,那么就更新s2的值为 i
算法描述:
void Locate(HuffmanTree HT,int n,int &s1,int &s2)
{
s1=1;
while(HT[s1].parent!=0) s1++;
for(int i=s1;i<=n;i++){
if(HT[i].weight<HT[s1].weight && HT[i].parent==0)
s1=i;
}
s2=1;
while(HT[s2].parent!=0 || s2==s1) s2++;
for(int i=s2;i<=n;i++){
if(HT[i].weight<HT[s2].weight && HT[i].parent==0 && i!=s1)
s2=i;
}
}
三、代码展示及运行截图
代码:
#include<bits/stdc++.h>
using namespace std;
//哈夫曼树的存储表示
typedef struct{
int weight;
int parent;
int lchild;
int rchild;
}HTNode,*HuffmanTree;
//选择未使用过的两个weight值最小的结点,并将下标的位置赋值给s1和s2
void Locate(HuffmanTree HT,int n,int &s1,int &s2)
{
s1=1;
while(HT[s1].parent!=0) s1++;
for(int i=s1;i<=n;i++){
if(HT[i].weight<HT[s1].weight && HT[i].parent==0)
s1=i;
}
s2=1;
while(HT[s2].parent!=0 || s2==s1) s2++;
for(int i=s2;i<=n;i++){
if(HT[i].weight<HT[s2].weight && HT[i].parent==0 && i!=s1)
s2=i;
}
}
//构造哈夫曼树
void CreatHuffmanTree(HuffmanTree &HT,int n)
{
if(n<=1) return;
int m=2*n-1;
HT=new HTNode[m+1];
for(int i=1;i<=n;i++){
cin>>HT[i].weight;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
for(int i=n+1;i<=m;i++){
HT[i].weight=0;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
/*初始化完成*/
for(int i=n+1;i<=m;i++){
int s1,s2;
Locate(HT,i-1,s1,s2);
HT[i].lchild=s1;HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight;
HT[s1].parent=i;HT[s2].parent=i;
}
}
int main()
{
HuffmanTree HT;
int n;
cout<<"请输入叶子结点的个数:";
cin>>n;
printf("请输入%d个已知权值:\n",n);
CreatHuffmanTree(HT,n);
printf("结点i weight parent lchild rchild\n");
for(int i=1;i<=2*n-1;i++){
printf("%d\t%d\t%d\t%d\t%d\n",i,HT[i].weight,HT[i].parent,HT[i].lchild,HT[i].rchild);
}
}