哈弗曼树的创建编码及译码

哈弗曼树创建并实现编码译码

采用C语言实现从文件读取数据,构造创建哈弗曼树,实现编码译码过程。

1.哈弗曼树的基本概念
哈弗曼树,又称为最优二叉树,是带权路径最短的树,可用来构造最优编码,用于信息传输,数据压缩等方面,是一种广泛的二叉应用。
需要了解的基本概念:
路径:从树的一个节点到另一个节点之间的分支序列构成两个节点的路径
路径程度:路径上分支的条数
树的路径长度:从跟节点到每个节点路径的长度之和
节点的权: 给数种节点赋予一个数值
带权路径长度:节点到树根间的路径长度与节点的权值的乘积
树的带权路径长度:树中所有叶子节点的带权路径长度之和,成为树的带权路径长度,记为WPL。
最优二叉树:在叶子节点个数n以及各叶子的权值确定的条件下,树的带权路径长度WPL最小的二叉树。
例如:如下三棵树的权值分别为{9,6,3,1}的四个叶子构成,其带权路径长度为:
《哈弗曼树的创建编码及译码》
可以验证:c树是有四个叶子且权值分别为{9,6,3,1}的一棵最优二叉树
2 . 哈弗曼编码原理:选择前缀编码,即:同一字符集中任何一个字符的编码都不是另一个字符编码的前缀。例如:对于字符集{A,B,C,D},编码集对应为{1,01,000,001}。另一方面,为了有效的压缩信息,则出现频率高的字符编码应该短一些,而出现字符频率小字符编码则长一些,观察哈弗曼树可以发现,权值大的叶子距根近,权值小的反之。因此,可以利用哈弗曼树的根到叶子节点的路径设计编码。每一个字符对应一个叶子,每一个权值对应出现频率。 哈弗曼编码是最优二进制前缀编码。
3. 哈弗曼译码原理:译码分解、识别、还原数据的过程。从哈弗曼树的根出发,根据每位编码的0或1确定进入左子树还是右子树,直到叶子节点。
4.C语言编程实现过程
– 哈弗曼树建立的数据结构:
weight:节点的权值
LChild:左孩子
RChild:右孩子
Parent:双亲
data:存储字符数据

typedef struct CodeNode
{
 char ch;
 char bits[9];//存放编码位置
 int len;//编码长度
}HuffmanCode[MAX];

变量声明:

int wt[MAX];//存放每个字符对应的权值
int Wt[MAX];
char s[MAX];//存放文件中的字符
char str[MAX];//存放文件中的字符种类
char chara[MAX];
int m;//字符种类数
int sum;//字符总个数
int cn=0;//字符频率
char filename[20];

提取文件中的字符,用哈希表统计字符频率,哈希表数组下标数值对应字符的ACSII码大小,哈希数组中对应存储权值

void  Get(huffmanTree Ht,chars[])
{   FILE *fp;

 char ch;
 int i=1;

 printf("请输入你要打开文件的名字:");
 scanf("%s",filename);
 fp=fopen(filename,"r");
 if(fp==NULL)
 { printf("\t\tCan not open thefile");
 system("pause");
 }
 else
  printf("\t\tOpen the filesuccess!\n");
 ch=fgetc(fp);

 while(ch!=EOF)
 {
  s[i++]=ch;//存储文件中的字符
  sum++;//存储文件中的字符总个数
     wt[ch]++;//存储字符权值
    ch=fgetc(fp);

 }

 fclose(fp);
}

统计字符种类数及对应的权值

int Statistic()
{   int i=1,j;
    for(j=1;j<MAX;j++)
        if(wt[j]!=NULL)
        {Wt[i++]=wt[j];
         cn++;
        }

    return cn;
}

选取最小与次小值,用来创建叶子

void select(huffmanTree h,int n,int *s1,int *s2)
{
    int i;
    int p,q;
    *s1=*s2=0;
    p=q=MAX;
    for(i=1;i<=n;i++)
    {
        if(h[i].parent==0&&h[i].weight<p)
        {
            q=p;
            *s2=*s1;
            p=h[i].weight;
            *s1=i;
        }
        else if(h[i].parent==0&&h[i].weight<q)
        {
            q=h[i].weight;
            *s2=i;
        }

    }
}

哈弗曼树的创建

void CreatHuffmanTree(huffmanTree Ht,int Wt[],int n,HuffmanCode HC)
{
    int i;
    int j=1;
    int s1,s2;
//对树进行初始化
    for(i=1;i<=2*n-1;i++)
    {    Ht[i].data='0';
        Ht[i].parent=0;
        Ht[i].Lchild=0;
        Ht[i].Rchild=0;
        Ht[i].weight=0;
    }
    for(i=1;i<=n;i++)
    {  
        Ht[i].weight=Wt[i]; //对叶子节点进行权值的赋值
    }
   for(i=n+1;i<=2*n-1;i++)
    {
            select(Ht,i-1,&s1,&s2);//选取最小与次小值
            Ht[i].weight=Ht[s1].weight+Ht[s2].weight;
            Ht[i].Lchild=s1;
            Ht[i].Rchild=s2;
            Ht[s1].parent=i;
            Ht[s2].parent=i;
    }
    for(i=1;i<MAX;i++)
      if(wt[i]!=0)
       HC[j++].ch=(char)i;
      for(i=1;i<=m;i++)
          Ht[i].data=HC[i].ch;
//打印哈弗曼树     
 printf("\tdata\tweight\tparebt\tLchild\tRchild\n");
    for(i=1;i<=2*n-1;i++)
    {   printf("\t%c",Ht[i].data);
        printf("\t%d",Ht[i].weight);
        printf("\t%d",Ht[i].parent);
        printf("\t%d",Ht[i].Lchild);
        printf("\t%d",Ht[i].Rchild);
        printf("\n");

    }
  for(i=1;i<MAX;i++)
      if(wt[i]!=0)
      HC[j++].ch=(char)i;
      printf("\n\n\t====>文件中的字符种类及所对应的权值<====\n");
      for(i=1;i<=m;i++)
          printf("\t\t<%c,%d>\n",HC[i].ch,Wt[i]);
      printf("\n");
      system("pause");
      system("cls");

      printf("\n\n\t\t\t哈夫曼树创建成功!\2\n\n\n\t\t\t\t\t\t");
      system("pause");

}

根据创建好的哈弗曼树对文件中含有的每种字符进行编码:

/*哈弗曼编码*/
void HuffmanEncoding(huffmanTree Ht,HuffmanCode HC)
{  
   int c,p,i;
   char cd[MAX];
   int start;
   cd[m]='\0';

   for(i=1;i<=m;i++)
   {  
       start=m;
       c=i;
       p=Ht[c].parent;
       while(p>0)
       {       --start;
           if (Ht[p].Lchild==c)
               cd[start]='0'; //左孩子编码为0
           else if(Ht[p].Rchild==c)
               cd[start]='1';//右孩子编码为1
           c=p;
           p=Ht[c].parent;
       }
       strcpy(HC[i].bits,&cd[start]);
       HC[i].len=m-start;

   }

}

对目标文件进行编码并存储至文件中,对文件中的字符依次进行编码,依次读取字符,已经编码的字符进行一一遍历比对,这种方法比较耗时,因为这个代码写的比较早,当时还没有接触算法,大家可以优化下

/*对文件进行编码*/
void coding(HuffmanCode HC,char *s )
{   system("cls");
    int i,j,k;
    FILE *fp1;

    printf("==>编码中...\n\n");
    printf("请输入你将要写入文件的名字:");
    scanf("%s",filename);
    fp1=fopen(filename,"w");
    if(fp1==NULL)
    {   printf("\t\tCan not open the file");
    system("pause");
    }
    else
        printf("\t\tOpen the file success!\n\n\n");
      printf("\t原文件为:");
      for(i=1;i<=sum;i++)
           printf("%c",s[i]);
      printf("\n");
    printf("\n\t文件的编码为:");
    for(j=1;j<=sum;j++)
            for(i=1;i<=m;i++)
        if(s[j]==HC[i].ch)
        {   
            for(k=0;k<=HC[i].len;k++)
            {   printf("%c",HC[i].bits[k]);
                fputc(HC[i].bits[k],fp1);//将编码写入文件中
            }
            break;
    }
system("pause");


    fclose(fp1);
    system("cls");
}

对已经编码的文件进行译码:

void decode(HuffmanCode HC,huffmanTree Ht)//对编码文件进行译码
{  
    FILE *fp1,*fp2;
    char ch;
    int p,k;
    char filename2[20];

    printf("==>译码中...\n\n");

    if((fp1=fopen(filename,"r"))==NULL)
    {
        printf("文件不能创建!");
        exit(1);
    }
    printf("请输入你将译码存放的文件名字:");
    scanf("%s",filename2);
        fp2=fopen(filename2,"w");
    if(fp2==NULL)
    {
        printf("文件不能创建!");
        exit(1);
    }

p=k=2*m-1;
ch=fgetc(fp1);

printf("译码为:");
while(ch!=EOF)
{   
    if(ch=='0')
        p=Ht[p].Lchild;
    else if(ch=='1')
        p=Ht[p].Rchild;
    if(Ht[p].data!='0')
    {
        printf("%c",Ht[p].data);
        fputc(Ht[p].data,fp2);
        p=k;
    }
        ch=fgetc(fp1);
}
        system("pause");
        printf("\n");
        fclose(fp1);
        fclose(fp2);
}

程序的主函数为:

int main()
{   
    huffmanTree Ht;
    HuffmanCode HC;


    printf("\t\t\t\t功能简介:\n");
    printf("\t\tNO.1 获取文件中的字符及频率,建立哈夫曼树\n");
    printf("\t\tNO.2 对原文件进行哈弗曼编码\n");
    printf("\t\tNO.3 编码后的文件译码\n");


    Get(Ht,s);
    m=Statistic();
    CreatHuffmanTree(Ht,Wt,m,HC);
    HuffmanEncoding(Ht,HC);

    coding(HC,s);
    decode(HC,Ht);
    return 0;
}

再次声明:本文章中的代码写于2013年左右,近期整理下,有的地方算法设计不是很合理,大家可以参考改进哈~

    原文作者:哈夫曼树
    原文地址: https://blog.csdn.net/u014524107/article/details/47001365
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞