算法思想

由哈夫曼树的定义可知,初始森林中共有n棵只含有根结点的二叉树,将当前森林中的两棵根结点权值最小的二叉树合并成一棵新的二叉树;每合并一次,森林中就减少一棵树,产生一个新结点。显然要进行n-1次合并,所以共产生n-1个新结点,它们都是具有两个孩子的分支结点。由此可知,最终求得的哈夫曼树中一共有2n-1个结点,其中n个结点是初始森林的n个孤立结点。并且哈夫曼树中没有度数为1的分支结点。可以利用一个大小为2n-1的一维数组来存储哈夫曼树中的结点。

算法实现

  1. 定义结构体类型
  1. char hmfcode[MAX];
  2. typedef struct {
  3. char data; //结点字符
  4. int weight; //权值
  5. int parent; //双亲结点
  6. int lchild; //左孩子结点
  7. int rchild; //右孩子结点
  8. } HTNode;
  1. 根据设计要求和实际需要定义的类型如下:
  1. typedef struct {
  2. char cd[N]; //存放哈夫曼码
  3. int start; //从 start 开始读 cd 中的哈夫曼码
  4. } HCode;
  1. 字符数统计函数:
  1. int Statistics(char *s,int cnt[],char str[]) {
  2. char *p;
  3. int i,j,k;
  4. for(i=1; i<=256; i++)
  5. cnt[i]=0;
  6. for(p=s; *p!='\0'; p++) {
  7. k=*p;
  8. cnt[k]++;
  9. }
  10. j=0;
  11. for(i=1,j=0; i<=256; i++)
  12. if(cnt[i]!=0) {
  13. j++;
  14. }
  15. return j;
  16. }
  1. 哈夫曼树创建函数:
  1. void CreateHT(HTNode ht[],int n,char str[],int cn[]) { //创建哈夫曼树函数
  2. for(int input=1; input<=256; input++) {
  3. str[input]=input;
  4. }
  5. int l=0;
  6. for(int output=1; output<=256; output++) {
  7. if(cn[output] !=0) {
  8. ht[l].data=str[output]; //按字母顺序将出现的字母依次存入数组 ht[]
  9. ht[l].weight=cn[output];
  10. l++;
  11. }
  12. }
  13. int i,k,lnode,rnode;
  14. int min1,min2;
  15. for (i=0; i<2*n-1; i++)
  16. ht[i].parent=ht[i].lchild=ht[i].rchild=0; //所有结点的相关域置初值 0
  17. for (i=n; i<2*n-1; i++) { //构造哈夫曼树
  18. min1=min2=MAX; //int 的范围是 -32768-32767
  19. lnode=rnode=0; //lnode 和 rnode 记录最小权值的两个结点位置
  20. for (k=0; k<=i-1; k++) { //选出每次外层循环最小权值的两个结点
  21. if (ht[k].parent==0) { //只在尚未构造二叉树的结点中查找
  22. if (ht[k].weight<min1) { //比 min1 小时
  23. min2=min1;
  24. rnode=lnode;
  25. min1=ht[k].weight;
  26. lnode=k;
  27. } else if (ht[k].weight<min2) { //比 min1 大,比 min2 小
  28. min2=ht[k].weight;
  29. rnode=k;
  30. }
  31. }
  32. }
  33. ht[lnode].parent=i;
  34. ht[rnode].parent=i; //两个最小节点的父节点是 i
  35. ht[i].weight=ht[lnode].weight+ht[rnode].weight; //两个最小节点的父节点权值为两个最小节点权值之和
  36. ht[i].lchild=lnode;
  37. ht[i].rchild=rnode; //父节点的左节点和右节点
  38. }
  39. }
  1. 根据哈夫曼树求哈夫曼编码:
  1. void CreateHCode(HTNode ht[],HCode hcd[],int n) {
  2. int i,p,c;
  3. HCode hc;
  4. for (i=0; i<n; i++) { //根据哈夫曼树求哈夫曼编码
  5. hc.start=n; //初始位置
  6. c=i; //从叶子结点 ht[i] 开始上溯
  7. p=ht[i].parent;
  8. while (p!=0) { // 循序直到树根结点结束循环
  9. hc.cd[hc.start--]=(ht[p].lchild)==c?'0':'1'; //左孩子记为 0,右孩子记为 1
  10. c=p;
  11. p=ht[p].parent; //与上句 c=i;p=ht[i].parent 同义,促进循环
  12. }
  13. hc.start++; //start 指向哈夫曼编码 hc.cd[] 中最开始字符
  14. hcd[i]=hc;
  15. }
  16. }
  1. 输出哈夫曼编码的列表:
  1. void outputHCode(HTNode ht[],HCode hcd[],int n) { //输出哈夫曼编码的列表
  2. int i,k;
  3. printf("输出哈夫曼编码 :\n");
  4. for (i=0; i<n; i++) { //输出 data 中的所有数据,
  5. printf("%c:\t",ht[i].data);
  6. for (k=hcd[i].start; k<=n; k++) { //输出所有 data 中数据的编码
  7. printf("%c",hcd[i].cd[k]); //从初最开始的字符起输出
  8. }
  9. printf("\n");
  10. }
  11. }
  1. 编码函数,将编码结果存入文件hmfcode.txt中:
  1. void editHCode(HTNode ht[],HCode hcd[],int n,char str[]) { //编码函数
  2. FILE* fp;
  3. fp = fopen("hmfcode.txt", "a+"); //打开文件,文件不存在则创建
  4. int i,j,k,l;
  5. printf("\n输出编码结果 :\n");
  6. for (i=0; i<MAX; i++)
  7. for (j=0; j<n; j++)
  8. if(str[i]==ht[j].data) { //循环查找与输入字符相同的编号,相同的就输出这个字符的编码
  9. for (k=hcd[j].start; k<=n; k++) {
  10. printf("%c",hcd[j].cd[k]);
  11. fputc(hcd[j].cd[k], fp);
  12. }
  13. break; //输出完成后跳出当前 for 循环
  14. }
  15. fputc('\n', fp);
  16. printf("\n");
  17. fclose(fp);
  18. }
  1. 译码函数(解压函数),将译码结果存入文件hmfcode.txt中:
  1. void deHCode(HTNode ht[],HCode hcd[],int n,char str[]) { //译码函数
  2. FILE* fp;
  3. fp = fopen("hmfcode.txt", "a+"); //打开文件,文件不存在则创建
  4. printf("输出译码结果为 :\n");
  5. int i,j,k,x,m=0;
  6. char code[MAX];
  7. for (i=0; i<MAX; i++)
  8. for (j=0; j<n; j++)
  9. if(str[i]==ht[j].data) { //循环查找与输入字符相同的编号,相同的就输出这个字符的编码
  10. for (k=hcd[j].start; k<=n; k++) {
  11. code[m]=hcd[j].cd[k]; //将输出的编码赋值到数组中
  12. m++;
  13. }
  14. break; //输出完成后跳出当前 for 循环
  15. }
  16. code[m]='#';
  17. //把要进行译码的字符串存入 code 数组中
  18. while(code[0]!='#')
  19. for (i=0; i<n; i++) {
  20. m=0; //m 为想同编码个数的计数器
  21. for (k=hcd[i].start,j=0; k<=n; k++,j++) { //j 为记录所存储这个字符的编码个数
  22. if(code[j]==hcd[i].cd[k]) //当有相同编码时 m 值加 1
  23. m++;
  24. }
  25. if(m==j) { //当输入的字符串与所存储的编码字符串个数相等时则输出这个的 data 数据
  26. printf("%c",ht[i].data);
  27. fputc(ht[i].data, fp);
  28. for(x=0; code[x-j]!='#'; x++) { //把已经使用过的 code 数组里的字符串删除
  29. code[x]=code[x+j]; //删除 j 个数,往前移动 j 位
  30. }
  31. }
  32. }
  33. fputc('\n', fp);
  34. printf("\n");
  35. fclose(fp);
  36. }
  1. 主函数
  1. #include <stdio.h>
  2. #include <string.h> //gets()函数需要
  3. #define N 256 //用 N 表示 50 叶节点数
  4. #define M 2*N-1 //用 M 表示节点总数 当叶节点数位 n 时总节点数为 2n-1
  5. #define MAX 32767
  6. int main() {
  7. FILE *fp = fopen("text.txt", "r");;
  8. int option;
  9. char st[MAX],sst[MAX];
  10. int cn[257];
  11. int n,i;
  12. printf("请选择:0:输入字符串\t1:打开文件text.txt:\n");
  13. scanf("%d",&option);
  14. if (!option) {
  15. getchar();
  16. printf("请输入字符串 (任意字符 ):\n");
  17. gets(st);
  18. } else {
  19. fgets(st, MAX, fp);
  20. printf("文件内容为:%s\n",st);
  21. }
  22. n=Statistics(st,cn,sst);
  23. for(i=0; i<99; i++)
  24. sst[i]=st[i];
  25. HTNode ht[M];
  26. HCode hcd[N];
  27. CreateHT(ht,n,st,cn);
  28. CreateHCode(ht,hcd,n);
  29. outputHCode(ht,hcd,n);
  30. editHCode(ht,hcd,n,sst);
  31. deHCode(ht,hcd,n,sst);
  32. printf("结果已成功保存在文件hmfcode.txt中!");
  33. fclose(fp);
  34. }

运行结果

  1. 输入字符串,实现编码,解码并将结果写入文件hmfcode.txt中:

哈夫曼编码实现文本压缩 - 图1

哈夫曼编码实现文本压缩 - 图2

  1. 从文件text.txt读取内容,实现编码,解码并将结果写入文件hmfcode.txt中:

哈夫曼编码实现文本压缩 - 图3

哈夫曼编码实现文本压缩 - 图4