霍夫曼编码算法与实现

来源:互联网 发布:打车软件司机版 编辑:程序博客网 时间:2024/06/14 23:26

本实验包含大量结构体的用法,需要先了解struct(自定数据类型),typedef(数据类型 别名),union(共用体)的用法。先参考《21天学通C语言 第六版》的第十一章

一、实验原理
1. Huffman编码算法
(1)将文件以ASCII字符流的形式读入,统计每个符号的发生频率;
(2)将所有文件中出现过的字符按照频率从小到大的顺序排列;
(3)每一次选出最小的两个值,作为二叉树的两个叶子节点,将和作为它们的根节点,这两个叶子节点不再参与比较,新的根节点参与比较;
(4)重复3,直到最后得到和为1的根节点;
(5)将形成的二叉树的左节点标0,右节点标1,把从最上面的根节点到最下面的叶子节点途中遇到的0, 1序列串起来,得到了各个字符的编码表示。
2. Huffman编码的数据结构设计
在程序实现中使用一种叫做二叉树的数据结构实现Huffman编码。
(1)霍夫曼节点结构

typedef struct huffman_node_tag  //节点数据类型  {      unsigned char isLeaf;  //是否为叶节点,1是0否      unsigned long count;   //信源文件中出现频数      struct huffman_node_tag *parent;  //父节点指针      union       {          struct    //如果不是叶节点,那么它为该节点左右子节点的指针          {              struct huffman_node_tag *zero, *one;          };          unsigned char symbol;   //如果是叶节点,那么它表示某个信源符号,这里用一个字节的8位二进制数表示      };  } huffman_node;  

(2)霍夫曼码结构

typedef struct huffman_code_tag  //码字数据类型  {      unsigned long numbits;  //码字的长度(bit)       /* 因为unsign char 类型是以 8 bit 来保存数据,所以码字8个8个一组保存在bits[]中,       例: 码字的第1位存于bits[0]的第1位,             码字的第2位存于bits[0]的第的第2位,            码字的第8位存于bits[0]的第的第8位,            码字的第9位存于bits[1]的第的第1位 */      unsigned char *bits;  //指向该码比特串的指针  } huffman_code;  

3霍夫曼编码流程

该程序包括两个工程,“huff_run”为主工程,其中包括了“huffcode.c”文件
“Huff_code”为库工程,其中包括了“huffman.c”文件
下面按照实验流程来进行分析
3.1 读入待编码的源文件
如下代码属于”huffcode.c”文件

main(int argc, char** argv)  {      char memory = 0;     //memory为1表示对内存数据进行操作,0表示不操作      char compress = 1;   //compress为1表示编码,0表示解码      int opt;      const char *file_in = NULL, *file_out = NULL;      //step1:add by yzhang for huffman statistics      const char *file_out_table = NULL;      //end by yzhang      FILE *in = stdin;      FILE *out = stdout;      //step1:add by yzhang for huffman statistics      FILE * outTable = NULL;   //输出的中间数据的表      //end by yzhang      while((opt = getopt(argc, argv, "i:o:cdhvmt:")) != -1) //读取命令行参数的选项      {          switch(opt)          {          case 'i':              file_in = optarg;    //i表示输入文件              break;          case 'o':              file_out = optarg;   //o表示输出文件              break;          case 'c':              compress = 1;        //c表示进行编码              break;          case 'd':              compress = 0;        //d表示进行解码              break;          case 'h':              usage(stdout);       //h表示输出参数用法说明              return 0;          case 'v':              version(stdout);     //v表示输出版本号信息              return 0;          case 'm':              memory = 1;          //m表示对内存数据进行操作              break;          // by yzhang for huffman statistics          case 't':              file_out_table = optarg;        //t表示输出的中间数据信息                break;          //end by yzhang          default:              usage(stderr);              return 1;          }      }      //如果输入文件给定,那么读取      if(file_in)      {          in = fopen(file_in, "rb");          if(!in)          {              fprintf(stderr,                      "Can't open input file '%s': %s\n",                      file_in, strerror(errno));              return 1;          }      }      //如果输出文件给定,那么创建      if(file_out)      {          out = fopen(file_out, "wb");          if(!out)          {              fprintf(stderr,                      "Can't open output file '%s': %s\n",                      file_out, strerror(errno));              return 1;          }      }      //by yzhang for huffman statistics      if(file_out_table)      {          outTable = fopen(file_out_table, "w");          if(!outTable)          {              fprintf(stderr,                  "Can't open output file '%s': %s\n",                  file_out_table, strerror(errno));              return 1;          }      }      //end by yzhang      if(memory)   //对内存数据进行编码或解码操作      {          return compress ?              memory_encode_file(in, out) : memory_decode_file(in, out);      }      //对文件进行编码或解码操作      if(compress)  //change by yzhang          huffman_encode_file(in, out,outTable);//step1:changed by yzhang from huffman_encode_file(in, out) to huffman_encode_file(in, out,outTable)      else      huffman_decode_file(in, out);      if(in)          fclose(in);      if(out)          fclose(out);      if(outTable)          fclose(outTable);      return 0;  }  

3.2 huffman.c第一次扫描,统计信源字符发生频率
因为是8比特,共256个信源符号,所以创建一个256个元素的指针数组,用以保存256个信源符号的频率。其下标对应相应字符的ASCII码。需要注意的是,源文件中不一定所有的信源符号都会出现,因此数组中存在空元素,而非空元素为待编码文件中实际出现的信源符号。

#define MAX_SYMBOLS 256    //typedef huffman_node* SymbolFrequencies[MAX_SYMBOLS];  //信源符号数组  //typedef huffman_code* SymbolEncoder[MAX_SYMBOLS];      //编码后的符号数组  
/* 第一次扫描,统计信源字符发生频率 */  static unsigned int  get_symbol_frequencies(SymbolFrequencies *pSF, FILE *in)  {      int c;      unsigned int total_count = 0;  //将总信源符号数初始化为 0      init_frequencies(pSF);   //将所有信源符号地址初始化为NULL(0)      //第一次扫描文件,统计输入文件中每个信源符号的发生频率      while((c = fgetc(in)) != EOF)      {          unsigned char uc = c;          if(!(*pSF)[uc])    // 如果是一个新符号,则产生该字符的一个新叶节点               (*pSF)[uc] = new_leaf_node(uc);          ++(*pSF)[uc]->count;  //如果已经是叶节点了,则字符发生频数+1          ++total_count;   //总信源符号数 +1       }      return total_count;  }  

3.3 建立Huffman树并计算符号对应的Huffman码字

(1) 将频率从小到大排序并建立Huffman树

(2) 递归遍历Huffman树,对存在的每个字符计算码字

3.4 将Huffman码表写入文件

4.1 读取码表并重建据此Huffman树

如下代码属于”huffman.c”文件

static huffman_node*  read_code_table(FILE* in, unsigned int *pDataBytes)  {      huffman_node *root = new_nonleaf_node(0, NULL, NULL);      unsigned int count;      if(fread(&count, sizeof(count), 1, in) != 1)// 读取码表中的符号数      {          free_huffman_tree(root);          return NULL;      }      count = ntohl(count);      //读取此编码表示的数据字节数      if(fread(pDataBytes, sizeof(*pDataBytes), 1, in) != 1)      {          free_huffman_tree(root);          return NULL;      }      *pDataBytes = ntohl(*pDataBytes);      while(count-- > 0)  //读取码表,由符号、码长、码字三部分组成      {          int c;          unsigned int curbit;          unsigned char symbol;   //符号          unsigned char numbits;  //码长          unsigned char numbytes; //字节数          unsigned char *bytes;          huffman_node *p = root;          if((c = fgetc(in)) == EOF)  //一个字节一个字节的读入          {              free_huffman_tree(root);              return NULL;          }          symbol = (unsigned char)c;          if((c = fgetc(in)) == EOF)          {              free_huffman_tree(root);              return NULL;          }          numbits = (unsigned char)c;          numbytes = (unsigned char)numbytes_from_numbits(numbits); //计算保存一个码长所需要的字节数          bytes = (unsigned char*)malloc(numbytes);  // 为读取码字分配相应的空间          if(fread(bytes, 1, numbytes, in) != numbytes)  // 读取码字          {              free(bytes);              free_huffman_tree(root);              return NULL;          }          //根据码表,进行huffman树的重建,由根节点至叶节点          for(curbit = 0; curbit < numbits; ++curbit)          {              if(get_bit(bytes, curbit))  // 判断当前读取位是否为’1’              {    //若当前读取位为'1'                  if(p->one == NULL)  //建立右子节点                  {                      //判断是否是当前码字的最后一位,若是,新建叶结点;若不是,则新建非叶结点。                      p->one = curbit == (unsigned char)(numbits - 1)                          ? new_leaf_node(symbol)                          : new_nonleaf_node(0, NULL, NULL);                      p->one->parent = p; //设置右子节点的父节点                  }                  p = p->one;              }              else   //若当前读取位为'0'              {                  if(p->zero == NULL)  //建立左子节点                  {                      p->zero = curbit == (unsigned char)(numbits - 1)                          ? new_leaf_node(symbol)                          : new_nonleaf_node(0, NULL, NULL);                      p->zero->parent = p;                  }                  p = p->zero;              }          }          free(bytes);      }      return root;   // 返回Huffman树的根结点,才可以进行之后的遍历  }  

4.2 读取Huffman码字,并根据huffman树进行解码输出
如下代码属于”huffman.c”文件

“`
int
huffman_decode_file(FILE *in, FILE *out)
{
huffman_node *root, *p;
int c;
unsigned int data_count;

root = read_code_table(in, &data_count); //读取码表  if(!root)      return 1;   // Huffman树建立失败  // 文件解码  p = root;  // data_count >0 :逻辑上仍有数据;(c = fgetc(in)) != EOF):文件中仍有数据  while(data_count > 0 && (c = fgetc(in)) != EOF)   {      unsigned char byte = (unsigned char)c;  //一个字节的码字      unsigned char mask = 1;    // mask用于逐位读出码字      while(data_count > 0 && mask)      {          //走huffman树,若当前字节为0,就走左子树,否则走右子树          p = byte & mask ? p->one : p->zero;          mask <<= 1;  //mask每个循环进行左移          if(p->isLeaf)  //走到叶结点,标明解码完毕          {              fputc(p->symbol, out);  //输出叶节点中存储的符号              p = root;     //转向根节点,进行下一个码字的解码              --data_count;  //将没解码的码字数-1          }      }  }  free_huffman_tree(root);  // 所有码字均已解码输出,文件解码完毕  return 0;  

}
二、实验结果
选择9种不同格式类型的文件,使用Huffman编码器进行压缩得到输出的压缩比特流文件,并对各种不同格式的文件进行压缩效率的分析
分析结果如下:
1. 表格形式表示的实验结果
这里写图片描述
2. 各样本文件的概率分布图
这里写图片描述
3. 实验结果的分析
可以从表格和概率分布图看出,huffman编码的平均码长小于且很接近信源熵,信源熵一定小于8bit/sym
当文件分布概率越不均匀,编码效率越高(如yuv文件);当文件分布概率比较均匀时,压缩效果并不理想

0 0
原创粉丝点击