第一次听说赫夫曼树是数据结构课上,但是听老师讲完以后发现,这不就是离散数学里面的最优二叉树嘛,在学习离散数学的时候觉得为什么要这么麻烦的存储几个数是数组不好用了还是链表不够玩了,后来学了树状数组发现树的结构方便了我们进行区间查询的时间复杂度,学了哈夫曼树才知道原来最优二叉树还能用来编码和译码,还能进行压缩编码
此时我的心里只有两个字
==============卧槽 ! 牛逼!
那怎么牛逼的数据结构难不难呐,我觉得说难不难,说简单也简单
首先我们要知道一颗赫夫曼树是怎么样组成的,就是他最基础的组成部分,其实就是一个结构体
typedef struct
{
char ch; //字母与编码
int weight; //权重
int parent,lchild,rchild; //父亲与左右孩子
}HTNode,*HuffmanTree;
这个结构体包含了三部分
1.字母:存储需要编码的字母:
2.权重:这个我理解为优先级,就比如有一个字符用的频率很高,我们可以给他一个很高的优先级,这样他就会被放在一个比较浅的位置,这样我们敲一次两次就能把这个字符输出;
3。父亲与左右孩子:这三个值分别存储每个节点的父节点和子节点的,没什么好说的;
接下来我们理一理怎样建一颗赫夫曼树
void CreateHuffmanTree(HuffmanTree &HT,int w[],char ch[],int n)
{
// w存放n个字符的权值(均>0),构造哈夫曼树HT,
int i, m,s1, s2;
m = 2 * n - 1;
HT = (HuffmanTree)malloc((m+1) * sizeof(HTNode)); // 0号单元不用
//设有一组权值存放于数组w[]中,对应的字符在数组ch[]中
for (i=1; i<=n; i++)
{
HT[i].weight=w[i-1];
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
HT[i].ch =ch[i-1];
}
//数组HT后n-1个元素先清空
for (i=n+1; i<=m; i++)
{
HT[i].weight=0;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
HT[i].ch='\0';
}
for (i=n+1; i<=m; i++) // 建哈夫曼树
{ 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;
}
}
有n个数据,则我们需要申请2n-1个空间,因为怕出错,所以我们可以申请2n个空间,然后0号空间弃置,前n个位置存放n个叶节点,然后将后n-1个节点清空,从n+1开始,先找到最小的没用过的两个节点,将这两个节点的父节点的值赋值为i,表示他们父节点就在i这个位置,将第i个节点的孩子节点分别赋s1,s2,权值为s1,s2的和。
不知道你们有没有注意到在上面那个函数里我们调用了一个select函数,他的功能是找到最小的两个没有用过的节点
int min(HuffmanTree HT, int n)
{//返回i个节点中权值最小的树的根节点序号,函数select()调用
int j,fla;
int k=99999;
for(j=1;j<=n;j++)
if(HT[j].weight<k&&HT[j].parent==0)//HT[j]是树的根节点
{
k=HT[j].weight;
fla=j;
}
HT[fla].parent=1;
return fla;
}
void Select(HuffmanTree HT,int n, int &s1, int &s2)
{ //在n个节点中选择2个权值最小的树的根节点序号,s1是其中序号小的那个
int j;
s1=min(HT,n);
s2=min(HT,n);
/*if(s1>s2)
{
j=s1;
s1=s2;
s2=j;
}*/
}//end_Select
这个函数其实很简单就是找到最小值而已,相信没有人是不会的,唯一需要注意的就是我们需要找到(没有用过的节点),我的做法就是判断它父亲是否为零,若父亲不为零那就是已经用过了;
到这里我们已经建立了一颗哈夫曼树,接下来我们讲讲怎么求每个字符的赫夫曼编码
void HTCoding(HuffmanTree HT,HuffmanCode &HC,int n)
{
// 从叶子到根逆向求每个字符的哈夫曼编码
int i,j,k, start;
int f;
int c;
char * cd;
HC=(HuffmanCode)malloc((n)*sizeof(char *));
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[f].lchild==c) cd[--start] = '0';
else cd[--start] = '1';
}
HC[i-1]=(char *)malloc((n-start)*sizeof(char));
for(j=start,k=0;j<n;j++,k++)// 从cd复制编码(串)到HC
HC[i-1][k]=cd[j];
}
free(cd); // 释放工作空间
}//end_HTCoding
在前面我们已经建好了一颗赫夫曼树,但是这也仅仅是一棵树而已,我们还要根据这颗树求出每个字符的编码。
这里选择的方法是从叶节点到根节点逐级编码,找到当前节点的父节点,然后根据父节点的双子节点的位置判断当前节点是左孩子还是右孩子,不断重复这一个过程直到找不到父节点为止,即一直进行到根节点为止。把n个叶子结点遍历完就完成了全部的编码工作
求平均编码长度,
double AverageLenght(HuffmanTree HT,HuffmanCode HC,int n)//求平均编码长度
{//补充完整
double sum,suma;
sum=0;
suma=0;
for(int j=0;j<n;j++)
{
sum+=HT[j+1].weight*strlen(HC[j]);
suma+=HT[j+1].weight;
/*printf("%lf\n",HT[j+1].weight);
printf("%s\n",HC[j]);
printf("%lf\n",sum);
printf("%lf\n",suma);
printf("------------\n");*/
}
double num;
num= (double)sum*1.0/suma;
/*printf("%lf\n",sum);
printf("%lf\n",suma);
printf("%lf\n",num);
printf("------------\n");*/
return num;
}//end_AverageLenght
这个没啥好讲的,只要直到平均编码长度是什么意思就好了;代码实现其实很简单
最后我们讲讲解码;
void DeCode(HuffmanTree HT,int n)//解码
{
int i;
char endflag='#';
char ch;
i=2*n-1; /*从根结点开始往下搜索*/
scanf("%c",&ch); /*读入一个二进制码*/
while (ch!=endflag)
{
if (ch=='0')
i=HT[i].lchild;
else i=HT[i].rchild;
if(HT[i].lchild==0) /*tree[i] 是叶子结点*/
{
printf("%c",HT[i].ch);
i=2*n-1;
}
scanf("%c",&ch);
}
if ((HT[i].lchild!=0) && (i!=2*n-1)) //电文读完但没到叶子结点
printf("\n未能完全解码\n");
printf("\n");
}//end_DeCode
首先我们读入一串二进制编码,从根节点开始,如果是‘ 0’我们找左孩子‘1’我们找右孩子,直到找到对应的叶节点,记录当前叶节点的字母,然后重新从根节点开始查找,直到找完所有编码;
-----------------------------------------------------================================-------------------------------------------------------
至此我们建立了一颗赫夫曼树,求了平均编码长度,并建立了一套编码和解码系统
最后完整代码镇楼
#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <malloc.h>
#include <cstring>
typedef struct
{
char ch; //字母与编码
int weight; //权重
int parent,lchild,rchild; //父亲与左右孩子
}HTNode,*HuffmanTree;
typedef char **HuffmanCode;
//以下为函数原型声明
void CreateHuffmanTree(HuffmanTree &HT,int w[],char ch[],int n); //构造HuffmanTree
void Select(HuffmanTree HT,int n, int &s1, int &s2); //选择两个权重最小的无父亲的结点
int min(HuffmanTree HT,int n);
void HTCoding(HuffmanTree HT,HuffmanCode &HC,int n); //利用HuffmanTree对字符编码
void PrintCode(HuffmanCode HC,int n,char ch[]);//输出编码
double AverageLenght(HuffmanTree HT,HuffmanCode HC,int n); //求平均编码
//长度
void DeCode(HuffmanTree HT,int n);//解码
int main()
{ int n;
int i;
char arrch[20];
int arrweight[20];
double avlength;
char ch;
HuffmanTree HT; //HT是一个指针变量,用于指向HuffmanTree
HuffmanCode HC; //HC是一个指针变量,用于存放对应字符的编码
scanf("%d",&n);//输入字符个数
while((ch=getchar())!='\n');
if(n>20||n<2)
exit(0); //输入的字符数超出要求范围退出;
for(i=0;i<n;i++) //输入字符和对应的权重
{
scanf("%c",&arrch[i]);
scanf("%d",&arrweight[i]);
while((ch=getchar())!='\n');
}
CreateHuffmanTree(HT,arrweight,arrch,n);//构造HuffmanTree
HTCoding(HT,HC,n);//利用HuffmanTree对字符编码
PrintCode(HC,n,arrch); //输出编码
avlength=AverageLenght(HT,HC,n);//求平均编码长度
printf("平均编码长度为:%f\n",avlength);
DeCode(HT,n);//解码
for(i=0;i<n;i++)
free(HC[i]);
free(HC);
free(HT);
return 0;
}//end_main
void CreateHuffmanTree(HuffmanTree &HT,int w[],char ch[],int n)
{
// w存放n个字符的权值(均>0),构造哈夫曼树HT,
int i, m,s1, s2;
m = 2 * n - 1;
HT = (HuffmanTree)malloc((m+1) * sizeof(HTNode)); // 0号单元不用
//设有一组权值存放于数组w[]中,对应的字符在数组ch[]中
for (i=1; i<=n; i++)
{
HT[i].weight=w[i-1];
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
HT[i].ch =ch[i-1];
}
//数组HT后n-1个元素先清空
for (i=n+1; i<=m; i++)
{
HT[i].weight=0;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
HT[i].ch='\0';
}
for (i=n+1; i<=m; i++) // 建哈夫曼树
{ 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;
}
}
int min(HuffmanTree HT, int n)
{//返回i个节点中权值最小的树的根节点序号,函数select()调用
int j,fla;
int k=99999;
for(j=1;j<=n;j++)
if(HT[j].weight<k&&HT[j].parent==0)//HT[j]是树的根节点
{
k=HT[j].weight;
fla=j;
}
HT[fla].parent=1;
return fla;
}
void Select(HuffmanTree HT,int n, int &s1, int &s2)
{ //在n个节点中选择2个权值最小的树的根节点序号,s1是其中序号小的那个
int j;
s1=min(HT,n);
s2=min(HT,n);
/*if(s1>s2)
{
j=s1;
s1=s2;
s2=j;
}*/
}//end_Select
void HTCoding(HuffmanTree HT,HuffmanCode &HC,int n)
{
// 从叶子到根逆向求每个字符的哈夫曼编码
int i,j,k, start;
int f;
int c;
char * cd;
HC=(HuffmanCode)malloc((n)*sizeof(char *));
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[f].lchild==c) cd[--start] = '0';
else cd[--start] = '1';
}
HC[i-1]=(char *)malloc((n-start)*sizeof(char));
for(j=start,k=0;j<n;j++,k++)// 从cd复制编码(串)到HC
HC[i-1][k]=cd[j];
}
free(cd); // 释放工作空间
}//end_HTCoding
void PrintCode(HuffmanCode HC,int n,char ch[]) //输出编码
{ //补充完整
int j;
printf("编码为:");
printf("%c %s\n",ch[j],HC[j]);
for(j=1;j<n;j++)
printf(" %c %s\n",ch[j],HC[j]);
}//end_PrintCode
double AverageLenght(HuffmanTree HT,HuffmanCode HC,int n)//求平均编码长度
{//补充完整
double sum,suma;
sum=0;
suma=0;
for(int j=0;j<n;j++)
{
sum+=HT[j+1].weight*strlen(HC[j]);
suma+=HT[j+1].weight;
/*printf("%lf\n",HT[j+1].weight);
printf("%s\n",HC[j]);
printf("%lf\n",sum);
printf("%lf\n",suma);
printf("------------\n");*/
}
double num;
num= (double)sum*1.0/suma;
/*printf("%lf\n",sum);
printf("%lf\n",suma);
printf("%lf\n",num);
printf("------------\n");*/
return num;
}//end_AverageLenght
void DeCode(HuffmanTree HT,int n)//解码
{
int i;
char endflag='#';
char ch;
i=2*n-1; /*从根结点开始往下搜索*/
scanf("%c",&ch); /*读入一个二进制码*/
while (ch!=endflag)
{
if (ch=='0')
i=HT[i].lchild;
else i=HT[i].rchild;
if(HT[i].lchild==0) /*tree[i] 是叶子结点*/
{
printf("%c",HT[i].ch);
i=2*n-1;
}
scanf("%c",&ch);
}
if ((HT[i].lchild!=0) && (i!=2*n-1)) //电文读完但没到叶子结点
printf("\n未能完全解码\n");
printf("\n");
}//end_DeCode