哈夫曼编码与解码

“哈夫曼编码与解码”这一项目一直在笔者计划中,但在诸多原因(实则懒散)的作用下,一拖再拖,直至近期,才总算肯动手完成了,哎,也算是“‘懒’海无涯,回头是岸”了吧。

对于“哈夫曼编码与解码”这一项目,网上实现的方法很多,也颇有高效、简洁和可实用性强等优点,只是笔者想着检验检验自己能力,也就没有参考网上方法,几乎九成以上自主完成,所以程序看起来可能有些累赘以及考虑不周之处,而且由于能力有限,也对项目做了些简化(具体下面功能介绍中会说明),所以经供参考吧,若有高见,欢迎指出。

功能介绍

这个项目简而言之就是“编码”与“解码”,也即是“加密”与“解密”,而所谓的哈夫曼编解码则是其中一种方式。把所需发送的内容通过哈夫曼编码转为密文,然后发送给对方,而对方收到密文后通过这一编码规则相应地将密文解码为明文,即是这一项目的总体功能。
然后,正如前面所说,能力有限嘛,也就对项目做了一些简化,比如编码与解码处于同一程序中(个人觉得理想情况下编码与解码应处于不同程序中,比如C/S或者C/S/C的C/C中,只是这样一来传递编码规则会有一些问题),又比如限制了文章长度及其有效内容(只能为字母与数字,其他则视为分隔符)。
在简化情况下,现在来详细介绍下这一项目的功能或者说总体实现方法。
(一)首先读取原文,将其中字母统统转化为小写字母,数字部分保持不变,而其他字符统统转化为分隔符之空格,处理后保存在数组中。

/*
 *获取文章内容并做一定处理
 *输入参数:原文的地址,保存文章内容的地址
 *返回值:无
 * */
void get_article_content(char str[],char art_content[])
{
  int art_fd ;
  int ret,i;
  art_fd = open(str,O_RDONLY);
  for(i=0;;i++){
    ret = read(art_fd,art_content+i,1);
    if(-1 == ret ){
      perror("read article error!");
      exit(-1);
    }
    else if(0 == ret){
      break;
    }
  }
  art_content[i]='\0';
  printf("%s\n",art_content);

  for(i=0;i<strlen(art_content);i++){
    if(art_content[i]>='A' && art_content[i] <= 'Z'){
      art_content[i] = art_content[i] + 32;
    }
    else if((art_content[i]>='0' && art_content[i] <= '9')||
            (art_content[i]>='a' && art_content[i] <= 'z')){
      continue;
    }
    else if(art_content[i] != '\0'){
      art_content[i] = ' ';
    }
    else
      break;
  }
  printf("%s\n",art_content);
  close(art_fd);
}

(二)正如我们所知,为构建哈夫曼树,我们得知道各个字符的权值,所以在这我们对处理后的文章内容进行统计,计算各个字符的权值,并保存起来。

/*
 *计算各个字符的权值
 *输入参数:保存文章内容的数组指针,保存权值的数组指针
 *返回值:无
 * */
void cal_weight(char art_content[],char *weight)
{
  int i;
  memset(weight,0,ARTICLE_CHAR_TYPE);
  for(i=0;i<strlen(art_content);i++){
    //0~25存对应a~z
    if(art_content[i]>='a' && art_content[i] <= 'z')
      weight[art_content[i]-'a']++;
    //36存空格 
    else if(art_content[i]==' ')
      weight[36]++;
    //26~35存0~9
    else if(art_content[i]>='0' && art_content[i] <= '9')
      weight[art_content[i]-'0'+26]++;
    else
      break;
  }
  for(i=0;i<ARTICLE_CHAR_TYPE;i++)
    printf("%d  ",weight[i]);
  putchar('\n');
}

(三)前面准备工作做好了,现在就是构建哈夫曼树了。不过,在构建哈夫曼树之前,得先确定树的数据结构。

//定义哈夫曼树的存储结构
typedef struct
{
  char data;    //结点的值
  int weight;   //结点的权值
  int parent;   //结点的双亲
  int lch;      //左孩子
  int rch;      //右孩子
}HuffNode;
/*
 *构造哈夫曼树
 *输入参数:保留权值的数组指针
 *返回值:无
 * */
void HuffmanTree(HuffNode ht[],char weight[] )
{
  int i,k,left,right,min1,min2;


  //对哈夫曼树中的结点进行初始化
  for(i=0;i<2*ARTICLE_CHAR_TYPE;i++){
    ht[i].parent = ht[i].lch = ht[i].rch = -1;
  }
  for(i=0;i<2*ARTICLE_CHAR_TYPE;i++){
    if(i<26){
      ht[i].data = 'a'+ i;
      ht[i].weight = (int)weight[i];
    }
    else if(i<36){
      ht[i].data = i-26+'0';
      ht[i].weight = (int)weight[i];
    }
   else if(i == 36){
      ht[i].data = ' ';
      ht[i].weight = (int)weight[i];
    }
    else if(i<2*ARTICLE_CHAR_TYPE-1){
      ht[i].data = 0;
    }
  }

  for(i=ARTICLE_CHAR_TYPE;i<2*ARTICLE_CHAR_TYPE-1;i++){
    //在前n个结点中选取权值最小的两个结点构成一颗二叉树
    min1=min2=100;
    left=right= -1;
    for(k=0;k<i;k++){
      if(ht[k].parent == -1){
        //令min1和min2为最小的两个权值,left和right为权值最小的
        //两个结点的位置
        if(ht[k].weight < min1){
          min2 = min1;
          right = left;
          min1 = ht[k].weight;
          left = k;
        }
        else if(ht[k].weight < min2){
          min2 = ht[k].weight;
          right = k;
        }
      }

    }

    //把找到的两个结点构成一颗二叉树
    ht[left].parent = i;        //找到的两个结点的双亲为i
    ht[right].parent = i;
    ht[i].weight = ht[left].weight + ht[right].weight;//i的权值为两个结
点权值之和
    ht[i].lch = left;
    ht[i].rch = right;
  }
}

(四)树建好了,那就可以进行编码工作了。同样的,在编码之前,得先确定途中需要用到的暂存编码的数据结构。

//定义哈夫曼编码的存储结构
typedef struct
{
  char bit[ARTICLE_CHAR_TYPE];//存储叶子结点的二进制编码
  int start;    //二进制编码的起始位
}HuffCode;

具体编码规则如下:
编码方法:

  • (1)判断从左往右第一个叶子结点(存储哈夫曼树结构的数组的第一个结点)是左孩子还是右孩子,然后在“ 存储叶子结点的二进制编码”的数组bit[]中反向记录相应的编>码值(左0右1);
  • (2)由第一个叶子结点可以跳转至它的双亲结点,判断其双亲结点对于其双>亲结点的双亲结点(祖父)来说是左孩子还是右孩子,然后在“ 存储叶子结点的二进制编码”的数组bit[]中反向记录相应的编码值(左0右1);
  • (3)再根据索引值(结点中的双亲结点这一成员的值)跳转至其它的双亲结点,重复(2)操作,直至根结点(没有双亲结点,此处用“ 0”表示),相应的记录编码值0。
  • (4)至此第一个叶子结点编码完成,后续第二个、第三个等等编码方法同上。
/*
 * 构造哈夫曼编码
 *输入参数:保存哈夫曼树结构的数组指针,文章内容
 *输出参数:无
*/
void HuffmanCode(HuffNode ht[],char art_content[])
{
  int i,j,c,k,f;
  /*
   * i代表从左往右第几个叶子结点
   * c代表当前结点(叶子结点/跳转到的双亲结点)
   * k代表在记录编码值的数组中的第一个编码值(根结点的编码值)
   * f代表当前结点的双亲结点
   */
  int len=strlen(art_content);
  HuffCode cd[len];
  for(j=0;j<len;j++){

    for(i=0;i<ARTICLE_CHAR_TYPE;i++){
      if(art_content[j]==ht[i].data){

        cd[j].start = ARTICLE_CHAR_TYPE-1;
        c = i;
        f = ht[i].parent;
        while(f!= -1){
          if(ht[f].lch == c)
            cd[j].bit[cd[j].start]='0';
          else
            cd[j].bit[cd[j].start]='1';
          cd[j].start--;
          c = f;
          f = ht[f].parent;
        }
        cd[j].bit[cd[j].start]='0';
      }
    }
  }

  int fd = open("./ciphertext.txt",O_WRONLY|O_CREAT|O_TRUNC);
  if(-1 == fd){
    perror("open ciphertext.txt error!");
    exit(-1);
  }
  int ret;
  for(j=0;j<len;j++){

    ret = write(fd,cd[j].bit+cd[j].start,ARTICLE_CHAR_TYPE-cd[j].start);
    if(-1 == ret ){
      perror("write ciphertext.txt error!");
     exit(-1);
    }
  }

  close(fd);

}

(五)解码部分相对应于编码部分,根据左0右1这一规则,即可实现。

/*
 *哈夫曼解码
 *输入参数:哈夫曼结构,密文地址
 *返回值:无
 * */
void HuffRecode(HuffNode ht[],char str[])
{
  int fd_cipher,fd_recd;
  int f_size;
  int i,j,k,root,dest;
  char recode_art[ARTICLE_CHAR_NUM_MAX];
  //打开密文
  fd_cipher = open(str,O_RDONLY);
  if(fd_cipher == -1){
    perror("open ciphertext error!");
    exit(-1);
  }
  //打开或创建译文
  fd_recd = open("./new_article.txt",O_WRONLY|O_CREAT|O_TRUNC);
  if(fd_recd == -1){
    perror("open new_article.txt error!");
    exit(-1);
  }
  //计算文件大小,用于开辟存储密文内容的空间,然后将密文内容存入该空间
  f_size = lseek(fd_cipher,0,SEEK_END);
  char *cipher_content = (char*)malloc(sizeof(char)*f_size);
  lseek(fd_cipher,0,SEEK_SET);
  for(i=0;i<f_size;i++){
   read(fd_cipher,cipher_content+i,1);
  }

  //解码
  //找到根结点
  for(j=0;j< 2*ARTICLE_CHAR_TYPE-1;j++){
    if( -1== ht[j].parent){
      root = j;
      break;
    }
  }

  //找到目标结点
  k=0;
  for(i=1;i<f_size;i++){
    dest = root;
    j=root;
    do{
      if(cipher_content[i]== '0')
        dest = ht[j].lch;
      else if(cipher_content[i]== '1')
        dest = ht[j].rch;
      j = dest ;
      i++;
    }while(ht[dest].data == '\0');
    //将译文逐个保存起来
    recode_art[k++]=ht[dest].data;
  }
  recode_art[k]='\0';
  printf("%s\n",recode_art);
  //将译文保存到文件中
  int ret = write(fd_recd,recode_art,strlen(recode_art));
  if(-1==ret){
    perror("write new_article error! ");
    exit(-1);
  }
  close(fd_recd);
  close(fd_cipher);
}

完整代码如下

/*
 *这个哈夫曼程序具有编码解码功能
 */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#define  ARTICLE_CHAR_NUM_MAX  100
#define  ARTICLE_CHAR_TYPE     37

//定义哈夫曼树的存储结构
typedef struct
{
  char data;    //结点的值
  int weight;   //结点的权值
  int parent;   //结点的双亲
  int lch;      //左孩子
  int rch;      //右孩子
}HuffNode;

//定义哈夫曼编码的存储结构
typedef struct
{
  char bit[ARTICLE_CHAR_TYPE];//存储叶子结点的二进制编码
  int start;    //二进制编码的起始位
}HuffCode;

/*
 *获取文章内容并做一定处理
 *输入参数:原文的地址,保存文章内容的地址
 *返回值:无
 * */
void get_article_content(char str[],char art_content[])
{
  int art_fd ;
  int ret,i;
  art_fd = open(str,O_RDONLY);
  for(i=0;;i++){
    ret = read(art_fd,art_content+i,1);
    if(-1 == ret ){
      perror("read article error!");
      exit(-1);
    }
    else if(0 == ret){
      break;
    }
  }
  art_content[i]='\0';
  printf("%s\n",art_content);

  for(i=0;i<strlen(art_content);i++){
    if(art_content[i]>='A' && art_content[i] <= 'Z'){
      art_content[i] = art_content[i] + 32;
    }
    else if((art_content[i]>='0' && art_content[i] <= '9')||
            (art_content[i]>='a' && art_content[i] <= 'z')){
      continue;
    }
    else if(art_content[i] != '\0'){
      art_content[i] = ' ';
    }
    else
      break;
  }
  printf("%s\n",art_content);
  close(art_fd);
}
/*
 *计算各个字符的权值
 *输入参数:保存文章内容的数组指针,保存权值的数组指针
 *返回值:无
 * */
void cal_weight(char art_content[],char *weight)
{
  int i;
  memset(weight,0,ARTICLE_CHAR_TYPE);
  for(i=0;i<strlen(art_content);i++){
    //0~25存对应a~z
    if(art_content[i]>='a' && art_content[i] <= 'z')
      weight[art_content[i]-'a']++;
    //36存空格 
    else if(art_content[i]==' ')
      weight[36]++;
    //26~35存0~9
    else if(art_content[i]>='0' && art_content[i] <= '9')
      weight[art_content[i]-'0'+26]++;
    else
      break;
  }
  for(i=0;i<ARTICLE_CHAR_TYPE;i++)
    printf("%d  ",weight[i]);
  putchar('\n');
}

/*
 *构造哈夫曼树
 *输入参数:保留权值的数组指针
 *返回值:无
 * */
void HuffmanTree(HuffNode ht[],char weight[] )
{
  int i,k,left,right,min1,min2;


  //对哈夫曼树中的结点进行初始化
  for(i=0;i<2*ARTICLE_CHAR_TYPE;i++){
    ht[i].parent = ht[i].lch = ht[i].rch = -1;
  }
  for(i=0;i<2*ARTICLE_CHAR_TYPE;i++){
    if(i<26){
      ht[i].data = 'a'+ i;
      ht[i].weight = (int)weight[i];
    }
    else if(i<36){
      ht[i].data = i-26+'0';
      ht[i].weight = (int)weight[i];
    }
   else if(i == 36){
      ht[i].data = ' ';
      ht[i].weight = (int)weight[i];
    }
    else if(i<2*ARTICLE_CHAR_TYPE-1){
      ht[i].data = 0;
    }
  }

  for(i=ARTICLE_CHAR_TYPE;i<2*ARTICLE_CHAR_TYPE-1;i++){
    //在前n个结点中选取权值最小的两个结点构成一颗二叉树
    min1=min2=100;
    left=right= -1;
    for(k=0;k<i;k++){
      if(ht[k].parent == -1){
        //令min1和min2为最小的两个权值,left和right为权值最小的
        //两个结点的位置
        if(ht[k].weight < min1){
          min2 = min1;
          right = left;
          min1 = ht[k].weight;
          left = k;
        }
        else if(ht[k].weight < min2){
          min2 = ht[k].weight;
          right = k;
        }
      }

    }

    //把找到的两个结点构成一颗二叉树
    ht[left].parent = i;        //找到的两个结点的双亲为i
    ht[right].parent = i;
    ht[i].weight = ht[left].weight + ht[right].weight;//i的权值为两个结
点权值之和
    ht[i].lch = left;
    ht[i].rch = right;
  }
}
/*
 * 构造哈夫曼编码
 *输入参数:保存哈夫曼树结构的数组指针,文章内容
 *输出参数:无
*/

 */
void HuffmanCode(HuffNode ht[],char art_content[])
{
  int i,j,c,k,f;
  int len=strlen(art_content);
  HuffCode cd[len];
  for(j=0;j<len;j++){

    for(i=0;i<ARTICLE_CHAR_TYPE;i++){
      if(art_content[j]==ht[i].data){

        cd[j].start = ARTICLE_CHAR_TYPE-1;
        c = i;
        f = ht[i].parent;
        while(f!= -1){
          if(ht[f].lch == c)
            cd[j].bit[cd[j].start]='0';
          else
            cd[j].bit[cd[j].start]='1';
          cd[j].start--;
          c = f;
          f = ht[f].parent;
        }
        cd[j].bit[cd[j].start]='0';
      }
    }
  }

  int fd = open("./ciphertext.txt",O_WRONLY|O_CREAT|O_TRUNC);
  if(-1 == fd){
    perror("open ciphertext.txt error!");
    exit(-1);
  }
  int ret;
  for(j=0;j<len;j++){

    ret = write(fd,cd[j].bit+cd[j].start,ARTICLE_CHAR_TYPE-cd[j].start);
    if(-1 == ret ){
      perror("write ciphertext.txt error!");
     exit(-1);
    }
  }
  close(fd);
}

/*
 *哈夫曼解码
 *输入参数:哈夫曼结构,密文地址
 *返回值:无
 * */
void HuffRecode(HuffNode ht[],char str[])
{
  int fd_cipher,fd_recd;
  int f_size;
  int i,j,k,root,dest;
  char recode_art[ARTICLE_CHAR_NUM_MAX];
  //打开密文
  fd_cipher = open(str,O_RDONLY);
  if(fd_cipher == -1){
    perror("open ciphertext error!");
    exit(-1);
  }
  //打开或创建译文
  fd_recd = open("./new_article.txt",O_WRONLY|O_CREAT|O_TRUNC);
  if(fd_recd == -1){
    perror("open new_article.txt error!");
    exit(-1);
  }
  //计算文件大小,用于开辟存储密文内容的空间,然后将密文内容存入该空间
  f_size = lseek(fd_cipher,0,SEEK_END);
  char *cipher_content = (char*)malloc(sizeof(char)*f_size);
  lseek(fd_cipher,0,SEEK_SET);
  for(i=0;i<f_size;i++){
   read(fd_cipher,cipher_content+i,1);
  }

  //解码
  //找到根结点
  for(j=0;j< 2*ARTICLE_CHAR_TYPE-1;j++){
    if( -1== ht[j].parent){
      root = j;
      break;
    }
  }

  //找到目标结点
  k=0;
  for(i=1;i<f_size;i++){
    dest = root;
    j=root;
    do{
      if(cipher_content[i]== '0')
        dest = ht[j].lch;
      else if(cipher_content[i]== '1')
        dest = ht[j].rch;
      j = dest ;
      i++;
    }while(ht[dest].data == '\0');
    //将译文逐个保存起来
    recode_art[k++]=ht[dest].data;
  }
  recode_art[k]='\0';
  printf("%s\n",recode_art);
  //将译文保存到文件中
  int ret = write(fd_recd,recode_art,strlen(recode_art));
  if(-1==ret){
    perror("write new_article error! ");
    exit(-1);
  }
  close(fd_recd);
  close(fd_cipher);
}


int main(void )
{
  char art[ARTICLE_CHAR_NUM_MAX];
  char weight[ARTICLE_CHAR_TYPE];
  HuffNode ht[2*ARTICLE_CHAR_TYPE];
  HuffCode hcd[ARTICLE_CHAR_TYPE];

  get_article_content("./article.txt",art);
  cal_weight(art,weight);
  HuffmanTree(ht,weight);
  HuffmanCode(ht,art);
  HuffRecode(ht,"./ciphertext.txt");
  return 0;
}
  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值