赫夫曼树,又称最优树,是一类带权路径长度最短的树,有着广泛的应用。
设二叉树具有n个带权值的叶子结点,从根结点到各个叶子结点的路径长度与对应叶子结点权值的乘积之和叫做二叉树的带权路径长度。
对于一组带有确定权值的叶子结点,带权路径长度最小的二叉树称为最优二叉树。
构造赫夫曼树:
1、创建n个根结点,权值{w1,w2,,,wn},得森林{T1,T2,Tn};
2、在森林中选取根结点权值最小的两颗二叉树归并为新二叉树,新二叉树根结点权值为两权值之和。
3、将新二叉树加入森林,同时忽略被归并的两颗二叉树。
4、重复2和3,至森林只有一颗二叉树。该二叉树就是赫夫曼树。
应用:赫夫曼编码。
以前,进行快速远距离通信的主要手段是电报,即将需传送的文字转换成由二进制的字组成的字符串。当然,在传送电文时,希望总长尽可能地短。而赫夫曼编码就是一种最优前缀编码。
输入字符的数目以及相应的权值,输出对应的赫夫曼编码。
运行结果:
赫夫曼树的存储结构定义:含n个字符则赫夫曼树有2*n-1个结点,个数固定,编码和解码无增删,故赫夫曼树采用动态分配的顺序存储结构,构造树时从叶子结点逐步往上走,识别字符或者说解码时从根往下走,故结点要含双亲和左右孩子下标,当然还要有权值。
首先是辅助宏的定义:
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OVERFLOW -1
#define UNDERFLOW -2
#define NULL 0
typedef int Status;
typedef char TElemType;
所有字符的Huffman编码用含n个元素的 动态数组表示,每个元素指向一个字符串。
typedef char * *HuffmanCode;
赫夫曼树的存储结构定义:
//Huffman树的存储结构定义
typedef struct{
unsigned int weight;//权值
int v; //存树的大小
unsigned int parent,lchild,rchild;//双亲与左右孩子下标
}HTNode,*HuffmanTree;
算法思想:构造Huffman数HT,并求Huffman编码HC n为字符数 w为权值数组为所有结点开辟存储空间,初始化各叶结点和分支结点 构造Huffman树关键是确定各分支结点相关结点相关信息,求出最小的二叉树 据此设置当前分支结点各成员的值开辟空指针数组 开辟临时存放单个编码的数组 从各叶出发逆向寻根每向上一步都将当前编码符记录到临时数组 最后一个空位置 ,待到达根则临时数组中得到完整编码后将该编码复制到指针数组的恰当位置即可。
算法实现:
Status HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n){
/*构造Huffman数HT,并求Huffman编码HC n为字符数 w为权值数组
为所有结点开辟存储空间,初始化各叶结点和分支结点
构造Huffman树关键是确定各分支结点相关结点相关信息,求出最小的
二叉树 据此设置当前分支结点各成员的值
开辟空指针数组 开辟临时存放单个编码的数组 从各叶出发逆向寻根每
向上一步都将当前编码符记录到临时数组 最后一个空位置 ,待到达根
则临时数组中得到完整编码后将该编码复制到指针数组的恰当位置即可*/
if(n<=1)
return ERROR;
int m=2*n-1,i,j,f,start;//m计算Huffman的结点总数
unsigned int s1,s2,c;
char *cd;
HTNode *p;
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));//0单元不用 先存叶后分支
if(!HT)
exit(OVERFLOW);
for(p=HT+1,i=1;i<=n;i++,p++,w++){
//初始化叶子
p->weight=*w;
p->parent=0;
p->rchild=0;
p->lchild=0;
p->v=i;
}
for(;i<=m;i++,p++){
//初始化各分支结点 不确定先写0
p->weight=0;
p->parent=0;
p->rchild=0;
p->lchild=0;
p->v=0;
}
for(i=n+1;i<=m;i++){
//建Huffman树
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;
HT[i].v=HT[s1].v; //修改树大小
}
HC=(HuffmanCode)malloc((n+1)*sizeof(char *));//开辟n指针各指向一编码串
if(!HC)
exit(OVERFLOW);
cd=(char *)malloc(n*sizeof(char));//存编码的临时空间 编码最长n-1
if(!cd)
exit(OVERFLOW);
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]=(char *)malloc((n-start)*sizeof(char));//为第i个字符编码分配空间
strcpy(HC[i],&cd[start]);//复制到HC的相应位置
}
free(cd);//记得释放临时空间
return OK;
}
Select函数:在HT[1..i]中选择parent为0 且权最小的两节点 设下标分别为s1 s2
void Select(HuffmanTree HT,int i,unsigned int &s1,unsigned int &s2){
//在HT[1..i]中选择parent为0 且权最小的两节点 设下标分别为s1 s2
int flag1=0,flag2=0,t;//判断是否找到第一个与第二个数
for(int j=1;j<=i;j++){
if(!HT[j].parent){
if(!flag1){//找第一个parent为0的数
s1=j;
flag1=1;
}
else if(flag1&&!flag2){//找第二个parent为0的数
if(HT[j].weight<HT[s1].weight){//将其中小的赋给s1 大的赋给s2
s2=s1;
s1=j;
}
else
s2=j;
flag2=1;
}
else{
//如果当前权值小于最小值 修改最小值与次小值
if(HT[j].weight<HT[s1].weight){
s2=s1;
s1=j;
}
//如果当前权值小于此小值但是大于最小值 修改次小值
else if(HT[j].weight<HT[s2].weight)
s2=j;
}
}
}
if(HT[s1].v<HT[s2].v){//如果s1 所在的树大于树s2 交换s1 s2 字典序就是尽量使字典序小的字母在左边!!!
t=s1;
s1=s2;
s2=t;
}
}