#include<bits/stdc++.h>
#define Maxvalue 20
#define Maxleaf 15
#define Maxnode (2*Maxleaf-1)
using namespace std;
int n=0,len,j,m;
typedef struct{
int num; //存放字母的种类
char ch; //存放字母的个数
}inf;
inf info[Maxleaf]; //定义此类型数组,叶子信息leaf information
typedef struct{
int weight; //权值
int parent; //老豆
int lchild; //左孩子
int rchild; //右孩子
}HTnode;
HTnode Hofftree[Maxnode]; //定义此类型数组
typedef struct{
char cd[10]; //存储每个结点的哈夫曼编码
int start; //起始位置
}Hcode;
Hcode hcd[Maxleaf]; //定义此类型数组
void Count(){ //统计字符串中字母总个数以及各个字母的个数
int k,i;
char s[Maxvalue]; //定义串
printf("请输入字符串:\n");scanf("%s",&s); //输入字符串
len=strlen(s);printf("字符串字母的个数为:%d\n",len); //读出长度
info[0].ch=s[0]; //读取第一个字符
info[0].num=1; //并记录其字符出现次数为1
for(k=1;k<=len;k++){ //把整个字符串扫一遍
for(m=0;m<=n;m++) //n是当前已出现字符个数,info[n]是第n个出现字符的结构体,存种类与次数
if(info[m].ch==s[k]){ //枚举第M个已出现字符看与当前扫到的SK是否相同
++info[m].num; //s[k]与s[m]是同一个字母则num加1
break; //只要扫到与当前字符有重复就可以跳出(这样也算是剪枝吧)
}//解释上一行,例如dabaca,第一个a计数在下面三行+1,当k是3时SK是第二个a,当M=1扫到第一个A使num++后跳出,如果继续扫到M=5num++就会与当K=5时扫a重复
if(m>n){ //对当前sk如果在N+1个字符都没找到,说明有新的字母s[k]出现
info[++n].ch=s[k]; //存字符
info[n].num=1; //存次数
}
}
for(j=0;j<n;j++)printf("字符%c的个数为:%d\n",info[j].ch,info[j].num); //每个出现的字符的种类与次数输出
for(i=0;i<n;i++)Hofftree[i].weight=info[i].num; //第i字母的权值赋予第i节点
}
void CreatHT(HTnode Hofftree[]){//构造哈夫曼树:就是更新每个结点的四个参数
int lnode,rnode; //定义左右儿子编号
double min1,min2; //定义最大最小值
for(int i=0;i<=2*n-1;i++) //全部结点初始化
Hofftree[i].parent=Hofftree[i].lchild=Hofftree[i].rchild=-1; //所有结点老豆与左右儿子编号都置初值-1
for(int i=n;i<2*n-1;i++){ //因为0~n-1的点是叶子,所以n~2n-1是非叶子结点,现在开始构建非叶子结点
min1=min2=32767; //最小值与次小值最大化
lnode=rnode=-1; //lnode和rnode为最小权值的两个结点位置
for(int k=0;k<=i-1;k++) //遍历已构建的结点[0,i-1],一开始仅是叶子结点,之后每轮多加一个非叶子结点
if(Hofftree[k].parent==-1){ //当前结点老豆为空,即未构造二叉树
if(Hofftree[k].weight<min1){ //扫到K结点权值比最小值小
min2=min1; //把MIN1赋给MIN2,原最小变次小
rnode=lnode; //左儿变右儿
min1=Hofftree[k].weight; //更新MIN1最小值
lnode=k; //左儿变更为K
}
else if(Hofftree[k].weight<min2){ //扫到K结点值小于次小值,就是说在左右子节点权值之间
min2=Hofftree[k].weight; //更新MIN2值
rnode=k; //右儿子变更为K
}
}//以上代码k由0扫到i-1,目的是在尚未构造二叉树的节点中查找权值最小的两个结点,左子节点权值不小于右子节点
Hofftree[i].weight=Hofftree[lnode].weight+Hofftree[rnode].weight;//i结点权值是左右儿子权值和
Hofftree[i].lchild=lnode; //lnode是i左儿
Hofftree[i].rchild=rnode; //rnode是i右儿
Hofftree[lnode].parent=i; //i是lnode老豆
Hofftree[rnode].parent=i; //i是rnode老豆
}//如此i从n到(2*n-1)-1就把所有非叶子结点构建完成了,而且全部结点三个针都更新完,没有的针就-1
}
void printHT(HTnode Hofftree[]){ //打印哈夫曼树
printf("哈夫曼树:\nweight lchild rchild parent\n");//提示语
for(int i=0;i<2*n-1;i++)printf("%d\t%d\t%d\t%d\n",Hofftree[i].weight,Hofftree[i].lchild,Hofftree[i].rchild,Hofftree[i].parent);
}//有N个字符,即N个叶子,则非叶结点有N-1个,所以树有2N-1个点,依次输出每个结点的权值,左儿,右儿,老豆
void Creathcode(HTnode Hofftree[],Hcode hcd[]){ //求哈夫曼编码
int f,c; //当前叶子老豆,当前叶子编号
Hcode hc; //开HC临时变量来存,最后再赋给hcd[i],即第i个字符的hcd
for(int i=0;i<n;i++){ //所有叶子结点扫一次,即所有字符扫一次
hc.start=n; //临时变量的开始位置记为n(结合读取输出时可知其实就是空)
c=i; //复制结点编号
f=Hofftree[i].parent; //读出当前叶子的老豆
while(f!=-1){ //循环直到根节点,根节点的PARENT是-1
if(Hofftree[f].lchild==c) //当前节点是老豆结点的左孩子结点
hc.cd[hc.start--]='0'; //这个结点的最右边的位赋为0(哈夫曼码是由根走到叶,由左走到右的)
else //当前节点是老豆结点的右孩子结点
hc.cd[hc.start--]='1'; //这个结点的最右边的位赋为0(现在从叶往根走,所以要倒过来)
c=f; //老豆赋给当前结点编号,作为下一轮的儿子
f=Hofftree[f].parent; //原老豆的老豆记为当前老豆
}
hc.start++; //因为上面是hc.start--,就是说赋了值之后减了1,但是最后减了1之后那个位没有存值
hcd[i]=hc; //临时变量赋给hcd[i],因为i要留下来赋值,所以上面才用了c复制结点编号
}
for(int i=0;i<n;i++){ //扫一次全部字符
printf("%c的编码为\n",info[i].ch); //提示语
for(j=hcd[i].start;j<=n;j++) //hcd[i].start第i个字符结构体中存的哈夫曼码的开始位置
printf("%c",hcd[i].cd[j]); //hcd[i].cd[j]第i个字符结构体中存的哈夫曼码的第J位
printf("\n"); //换行
}
}
int main(){
Count(); cout<<endl;//统计所输入字符串中字母总个数以及各个字母的个数
CreatHT(Hofftree); cout<<endl;//构造哈夫曼树
printHT(Hofftree); cout<<endl;//打印哈夫曼树
Creathcode(Hofftree,hcd); cout<<endl;//求哈夫曼编码
}
3树-4哈夫曼树
最新推荐文章于 2020-06-06 18:51:09 发布