#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<conio.h>
#define N 1000
#define M 2*N-1///N个叶子结点生成2*N-1的结点
typedef char* huffmanCode[2*M];///哈夫曼编码
typedef struct///哈夫曼树结构体
{
int weight;///权值
int parent;///父节结点
int lchild;///左孩子
int rchild;///右孩子
}huffman[M+1];
typedef struct Node
{
int weight; ///叶子结点的权值
int len; ///叶子结点的二进制码的长度
char ch; ///叶子结点
}leafcode[N];
void creat_leaf(char *,int *,leafcode ,int *);///创建叶子结点
void creat_huffmantree(huffman ,leafcode ,int );///创建哈夫曼树
void creat_leafcode(huffman ,char *,huffmanCode ,leafcode ,int ,int );///创建叶子结点的编码
void All_code(char *,huffmanCode ,huffmanCode ,leafcode ,int ,int );///所有的字符的编码
void transform_huffman(huffman ,leafcode ,huffmanCode ,int ,int );///解码
void clear();///清屏
int main()
{
int choose;
int i,n=0,m=0;///n为叶子结点的个数,m为字符串ch[]的长度
char str[N]; ///输入的字符串
huffman Tree; ///哈夫曼树
huffmanCode leaf_code,all_code; ///leaf_code存放叶子结点的编码,all_code存放所有结点的编码
leafcode leaf_information; ///存放叶子结点的信息
do{
printf("\t\t\t**********************\n");
printf("\t\t\t*1.产生叶子结点信息 *\n");
printf("\t\t\t*2.创建哈夫曼树 *\n");
printf("\t\t\t*3.创建叶子结点的编码*\n");
printf("\t\t\t*4.译码 *\n");
printf("\t\t\t*0.退出 *\n");
printf("\t\t\t**********************\n");
printf("\t\t\t请输入你需要操作的数字:");
scanf("%d",&choose);
if(choose==0)
{
printf("\t\t\t欢迎下次再使用!\n");
break;
}
switch(choose)
{
case 1: printf("请输入一段字符串:");
getchar();///回车键会被gets吸收,所以添加一个getchar来吸收回车键
gets(str);
for(i=0;str[i]!='\0';i++)
if(str[i]>='A'&&str[i]<='Z')
str[i]+=32;
puts("字符串转小写:");puts(str);
creat_leaf(str,&m,leaf_information,&n);///产生叶子结点信息,m为字符串str的长度
puts("字符:权值");
for(i=1;i<=n;i++) ///输出叶子结点的字符与权值
printf(" %c:%d \n",leaf_information[i].ch,leaf_information[i].weight);
break;
case 2:creat_huffmantree(Tree,leaf_information,n); ///产生哈夫曼的树
puts("\torder\tweight\tparent\tlchild\trchild");
for(i=1;i<=2*n-1;i++) ///打印哈夫曼树的信息
printf("\t%d\t%d\t%d\t%d\t%d\n",i,Tree[i].weight,Tree[i].parent,Tree[i].lchild,Tree[i].rchild);
break;
case 3:creat_leafcode(Tree,str,leaf_code,leaf_information,m,n); ///创建叶子结点的编码
puts("字符:编码");
for(i=1;i<=n;i++)///打印每个字符的编码
printf(" %c:%s\n",leaf_information[i].ch,leaf_code[i]);
break;
case 4:All_code(str,leaf_code,all_code,leaf_information,n,m);///所有字符的编码都储存到all_code里,等待输出
puts("字符串的编码:");
for(i=0;i<m;i++)///输出刚才输入的字符串的编码
printf("%s",all_code[i]);
puts("\n解码如下:");
transform_huffman(Tree,leaf_information,all_code,n,m); ///解码
puts("");
break;
default:printf("输入错误,请重新输入\n");break;
}
clear();
}while(1);
return 0;
}
///产生叶子结点的字符和权值
void creat_leaf(char *ch,int *s,leafcode leaf_information,int *p)
{
int i,j,k;
int tag;
*p=0;///叶子节点个数
for(i=0;ch[i]!='\0';i++) ///统计字符出现个数,放入leaf_information
{
tag=1;///做标记
for(j=0;j<i;j++)///这个是循环是,第一次出现的字符已经把它相关的权值弄好了,然后去匹配它前面的如果有相同的话不再需要去计算它的权值了
if(ch[j]==ch[i])///如果有相同的就不需要再操作下面的
{
tag=0;
break;
}
if(tag)
{
leaf_information[++*p].ch=ch[i];///把这个字符储存到结构体数组
leaf_information[*p].weight=1;///进入这个条件说明是第一次进来,那么就初始化为1,也就是它本身自己
for(k=i+1; ch[k]!='\0'; k++)///去匹配这个字符的下个字符到结束为止出现了几次
if(ch[i]==ch[k])
leaf_information[*p].weight++;///如果有相同的话,权值就自增1
}
}
*s=i;///字符串长度
///操作完,也就把字符串的每个字符的信息都整理好了
}
///创建哈夫曼树
void creat_huffmantree(huffman ht,leafcode leaf_information,int n)
{
int i,j;
int s1,s2;
for(i=1;i<=n;i++)///初始化哈夫曼树
{
ht[i].weight=leaf_information[i].weight;
ht[i].parent=0;
ht[i].lchild=0;
ht[i].rchild=0;
}
for(i=n+1;i<=2*n-1; i++)///初始化即将使用的哈夫曼树结点
{
ht[i].weight=0;
ht[i].parent=0;
ht[i].lchild=0;
ht[i].rchild=0;
}
for(i=n+1;i<=2*n-1; i++)///开始合并
{
for(j=1;j<=i-1;j++)
if(!ht[j].parent)
break;
s1=j; ///找到第一个双亲为零的结点
for(;j<=i-1;j++)///找到第一个双亲为0的结点不代表它是最小的,再循环一遍去找到最小的
if(!ht[j].parent)
s1=ht[s1].weight>ht[j].weight?j:s1;///三目运算符,如果比之前那个结点小,那就更新s1
ht[s1].parent=i;///最小结点的父亲结点
ht[i].lchild=s1;///那么它就是父亲结点的左孩子,因为左孩子是比右孩子小的
for(j=1;j<=i-1;j++)///再找第二个,第二个也是以此类推的
if(!ht[j].parent)
break;
s2=j; ///找到第二个双亲为零的结点
for(;j<=i-1;j++)
if(!ht[j].parent)
s2=ht[s2].weight>ht[j].weight?j:s2;
ht[s2].parent=i;
ht[i].rchild=s2;
ht[i].weight=ht[s1].weight+ht[s2].weight;///找它父亲结点的两个孩子之后进行相加,也就是这两个叶子结点的权值
}
///操作完之后也就是是哈夫曼树创建完成
}
///创建叶子结点的编码
void creat_leafcode(huffman ht,char *ch,huffmanCode leaf_code,leafcode leaf_information,int m,int n)
{
int i,p,c,start;
char *code_temp;///临时的编码
code_temp=(char *)malloc(n*sizeof(char));///申请空间
code_temp[n-1]='\0';///末尾置0
for(i=1;i<=n;i++)
{
start=n-1; ///code_temp串每次从末尾开始
c=i;///叶子结点
p=ht[i].parent;///p在n+1到2n-1,p是叶子结点的父亲
while(p) ///沿父亲结点方向开始遍历,直到为0才结束,也就是我们会从底搜到最上面,这也是哈夫曼的核心思想
{
start--;///依次向前
if(ht[p].lchild==c)///如果这个父亲的左孩子是这个叶子结点,就赋值0
code_temp[start]='0';
else ///否则赋值1
code_temp[start]='1';
c=p;///保存这个父亲的结点的值,用在下一次的匹配
p=ht[p].parent;///这个的父亲结点的父亲结点的值
}
leaf_information[i].len=n-start; ///二进制码的长度包含末尾
leaf_code[i]=(char *)malloc((n-start)*sizeof(char));///查找到底之后就进行申请空间,申请n-start的空间
strcpy(leaf_code[i],&code_temp[start]);///将二进制字符串拷贝到指针数组leaf_code中,也就是把一个叶子结点的编码储存到leaf_code了
}
free(code_temp);///用完之后,就释放code_temp内存
///操作完之后,叶子结点的编码也就都储存到leaf_code里面了
}
///所有字符的编码
void All_code(char *ch,huffmanCode leaf_code,huffmanCode all_code,leafcode leaf_information,int n,int m)
{
int i,j;
for(i=0;i<m;i++)///字符串长度
{
for(j=1;j<=n;j++) ///从leaf_information[j].ch中查找与ch[i]相等的下标j
if(ch[i]==leaf_information[j].ch)
break;
all_code[i]=(char *)malloc((leaf_information[j].len)*sizeof(char));///这个字符找到之后,就申请它相应需要的长度
strcpy(all_code[i],leaf_code[j]); ///拷贝二进制编码到all_code里面,等下进行输出
}
///操作完之后就把刚才的字符串所有的编码都储存起来,等待输出
}
///解码
void transform_huffman(huffman ht,leafcode leaf_code,huffmanCode all_code,int n,int m)
{
int i=0,j,p;
while(i<m)///进行刚才输入的字符串的解码
{
p=2*n-1;///从父亲结点向下遍历直到叶子结点
for(j=0;all_code[i][j]!='\0'; j++)///all_code是存储了刚才字符串的所有编码
{
if(all_code[i][j]=='0')///如果编码是0,也就是它的左孩子
p=ht[p].lchild;
else///否则就是它的右孩子
p=ht[p].rchild;
}///走到叶子结点,也就是这个字符的编码,那么就输出这个叶子结点的字符
printf("%c",leaf_code[p].ch);
i++;///进行下一个
}
}
///清屏
void clear()
{
printf("请按任意键继续......");
getch();
system("cls");
}
哈夫曼编码
最新推荐文章于 2022-06-16 21:25:31 发布