在学习图之前,我们需要学习二叉树而学到二叉树我们就会学到哈夫曼树(最优二叉树)。哈夫曼树在我们生活中应用较为广泛,通常用在对数据的压缩中,在对数据进行传输时,我们为了提高传输效率就会使用到压缩,接下来我就来谈谈我对哈夫曼树的了解:
1.在形成哈夫曼树之前我们要先创建只有根节点的二叉树n个(数量根据需求确定),即表示生成哈夫曼树之后有n个叶子结点,并输入n次权值
2.创建哈夫曼树结构体,包含结点权值,双亲,左孩子,右孩子。
3.在已有的只含根节点的二叉树中,每次找到权重最小的两棵树进行结合形成一个新的二叉树加入数组中,并在数组中删去进行结合过的二叉树(权重最小的两棵树),结合次数共进行n-1次,最后形成的哈夫曼树共有2*n-1个结点(讲解的十分笼统,在编写代码时最好同时看书)
如果已经对如何构造哈夫曼树有了大致了解了,那么我们就直接看看在代码中是如何实现过程的:
第一个函数:对只有根节点的二叉树进行挑选,找出权值最小的两棵二叉树(该函数用于第二个函
数中(创建哈夫曼树))
void Select(HuffmanTree HT,int n,int *s1,int *s2)//选出只有根结点且权重最小的两个树
{
int a=999,b=999;
int m=1;
for(m=1;m<=n;m++)//选出最小的树
{
if(HT[m].parent==0)
{
if(HT[m].weight<a)
{
a=HT[m].weight;
*s1=m;
}
}
}
for(m=1;m<=n;m++)//选出第二小的树(权重可能与第一棵树相同)
{
if(HT[m].parent==0&&m!=*s1)//m!=*s1 即选的树不能是同一颗,但其他树的权重可能与这棵已被选的树相同
{
if(HT[m].weight<b)//选最小值
{
b=HT[m].weight;
*s2=m;
}
}
}
}
*第二个函数(生成哈夫曼树)*:
HuffmanTree CreatHuffmanTree(HuffmanTree HT,int n)//将只有根结点的树转化成哈夫曼树
{
int i;
if(n<=1)
return;
int m=2*n-1;//m即为生成哈夫曼树中所有结点的个数
HT=(HuffmanTree)malloc(sizeof(HTNode));
for(i=1;i<=m;i++)//将所有结点初始化
{
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
printf("请输入各根结点的权重:\n");
for(i=1;i<=n;i++)
scanf("%d",&HT[i].weight);
for(i=n+1;i<=m;++i)//从n+1到m,即为生成哈夫曼树所要结合的次数
{
int s1=999,s2=999;
Select(HT,i-1,&s1,&s2);
printf("两孩子下标:%d %d--两孩子权重:%d %d\n",s1,s2,HT[s1].weight,HT[s2].weight);
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;
}
return HT;
}
哈夫曼树已经创建完成后,接下来的函数就是一些对哈夫曼树功能的实现:
第三个函数:先序遍历哈夫曼树
void Firstsearch(HuffmanTree T,int n) //先序遍历生成的哈夫曼树
{
printf("...%d...孩子结点下标:%d %d\n",T[n].weight,T[n].lchild,T[n].rchild);
if(T[n].lchild!=0)
Firstsearch(T,T[n].lchild); //递归遍历左子树
if(T[n].rchild!=0)
Firstsearch(T,T[n].rchild); //递归遍历右子树
}
第四个函数:对哈夫曼树深度的探索
每次探索时,在使用了该函数时就会派出两个小兵x,y分别向该结点的左孩子和右孩子进行探索,最后返回x,y中最大值。
int depth(HuffmanTree T,int n)//计算哈夫曼树的深度
{
int x,y;//x前往左孩子结点,y前往右孩子结点
if(T[n].lchild!=0)
{
x=1;
x=x+depth(T,T[n].lchild);
}
else
x=0;
if(T[n].rchild!=0)
{
y=1;
y=y+depth(T,T[n].rchild);
}
else
y=0;
if(x>=y)//比较探索的x和y,只需返回最大值即为深度
return x ;
else
return y;
}
第五个函数:计算哈夫曼树的带权路径长度
int WPS(HuffmanTree T,int n,int flag)//计算树的带权路径长度
{
int wps=0;
int a=0,b=0;
if(T[n].lchild!=0||T[n].rchild!=0)
{
flag++;
if(T[n].lchild!=0)
a=WPS(T,T[n].lchild,flag);
if(T[n].rchild!=0)
b=WPS(T,T[n].rchild,flag);
}
else
wps=flag*T[n].weight;
return wps+a+b;
}
第六个函数:将创建好的哈夫曼树用广义表达式表达出来
void Expression(HuffmanTree T,int n)//哈夫曼树广义表表达式
{
int a=T[n].lchild;//取出左右孩子结点下标
int b=T[n].rchild;
if(T[n].lchild!=0)
{
printf("(%d",T[a].weight);
Expression(T,T[n].lchild);
}
if(T[n].rchild!=0)
{
printf(",%d",T[b].weight);
Expression(T,T[n].rchild);
printf(")");
}
}
由于能力有限不能只在函数中编写完成,我还在主函数中掺加了两行代码来同时表达广义表表达式
如果还需要对哈夫曼树进行更多的操作,可以自行编写函数。在对哈夫曼树进行操作的函数中,大部分函数都运用了递归的思想,需要大家对递归有所了解掌握才能更好的理解代码含义。
最后献上直接编写代码运行结果的截图: