Huffman编解码

来源:互联网 发布:为知笔记活动 编辑:程序博客网 时间:2024/06/04 19:26
一、背景知识及相关公式
1、  熵,又称为“信息熵” (Entropy)

1) 在信息论中,熵是信息的度量单位。信息论的创始人 Shannon 在其著作《通信的数学理论》中提出了建立在概率统计模型上的信息度量。他把信息定义为“用来消除不确定性的东西”。

2) 一般用符号H  表示,单位是比特。对于任意一个随机变量X,它的熵定义如下:H(X)= - ∑ P(X)log2[P(X)]。

3)  变量的不确定性越大,熵也就越大。换句话说,了解它所需要的信息量也就越大。

2、  Huffman 编码

1)  Huffman  Coding  (霍夫曼编码)是一种无失真编码的编码方式,Huffman 编码是可变字长编码(VLC)的一种。

2)  Huffman  编码基于信源的概率统计模型,它的基本思路是,出现概率大的信源符号编长码,出现概率小的信源符号编短码,从而使平均码长最小。

3)  在程序实现中常使用一种叫做树的数据结构实现 Huffman 编码,由它编出的码是即时码。

3  、Huffman  编码的方法

1) 统计符号的发生概率;

2)  把频率按从小到大的顺序排列

3)  每一次选出最小的两个值,作为二叉树的两个叶子节点,将和作为它们的根节点,这两个叶子节点不再参与比较,新的根节点参与比较;

4)  重复 3,直到最后得到和为 1 的根节点;

5)  将形成的二叉树的左节点标 0,右节点标 1,把从最上面的根节点到最下面的叶子节点途中遇到的 0,1 序列串起来,就得到了各个符号的编码。

二、实验过程

1 、 数据结构
1) Huffman 结点

typedef struct huffman_node_tag {   unsigned char is Leaf;     /* 是否为叶结点*/   unsigned long count;     /* 信源中出现频数 */   struct huffman_node_tag *parent;     /* 父节点指针 */   union     {    struct    {     struct huffman_node_tag *zero, *one;  /*如果不是树叶,则此项为该结点左右孩子的指针*/    };    unsigned char symbol; /*如果是树叶,为某个信源符号 */   }; } huffman_node; 
2)Huffman码字结点
typedef struct huffman_code_tag {   /*  码字的长度(单位:位) */   unsigned long numbits;  /*  码字,       码字的第 1 位存于 bits[0]的第 1 位,           码字的第 2 位存于 bits[0]的第 2 位,      码字的第 8 位存于 bits[0]的第 8 位,      码字的第 9 位存于 bits[1]的第 1 位  */   unsigned char *bits; } huffman_code; 
2、Huffman 编码

在实验时采用静态链接库,程序文件中包含两个工程,其中主工程需选择Win32 Console Application, 库工程需选择Win32 Static Library。

1)编码流程

2)  第一次扫描,统计信源字符发生频率(8 比特,共 256 个信源符号)

1> 创建一个 256 个元素的指针数组,用以保存 256 个信源符号的频率。其下标对应相应字符的 ASCII 码。

2>  数组中的非空元素为当前待编码文件中实际出现的信源符号。

3>  程序代码如下:

typedef huffman_node* Symbol Frequencies[MAX_SYMBOLS]; Symbol Frequencies sf; static unsigned int get_symbol_frequencies(Symbol Frequencies *pSF, FILE *in) {   int c;     /*  总信源符号数初始化为 0 */   unsigned int total_count = 0;   /*  将所有信源符号地址初始化为 NULL(0) */   init_frequencies(pSF);   /*  第一遍扫描文件*/   while((c = fgetc(in)) != EOF) /*判断文件是否结束*/  {    unsigned char uc = c;    /*  如果是一个新符号,则产生该字符的一个新叶节点  */    if(!(*pSF)[uc])    (*pSF)[uc] = new_leaf_node(uc);    /*  当前字符出现的频数+1 */    ++(*pSF)[uc]->count;   /*  总信源符号数+1 */    ++total_count;   }   return total_count; } 
3)建立 Huffman 树并计算符号对应的 Huffman 码字

1>  按频率从小到大顺序排序并建立 Huffman 树

tatic Symbol Encoder* calculate_huffman_codes(Symbol Frequencies * pSF) {   unsigned int i = 0;   unsigned int n = 0;   huffman_node *m1 = NULL, *m2 = NULL;   Symbol Encoder *pSE = NULL;   /*按信源符号出现频率大小排序,小概率符号在前(pSF 数组中)即下标较小 */   qsort((*pSF), MAX_SYMBOLS, sizeof((*pSF)[0]), SFComp);   /* 得到当前待编码文件中所出现的信源符号的种类总数 */   for(n = 0; n < MAX_SYMBOLS && (*pSF)[n]; ++n)    ;   /* 建立 huffman 树。需要合并 n-1 次,所以循环 n-1 次 */    for(i = 0; i < n - 1; ++i)   {    /*将m1、m2 置为当前频数最小的两个信源符号*/    m1 = (*pSF)[0];    m2 = (*pSF)[1];   /*将m1、m2 合并为一个 huffman 结点加入到数组中,左、右孩子分别置为 m1、m2 的地址,频数为 m1、m2的频数之和*/    (*pSF)[0] = m1->parent = m2->parent = new_nonleaf_node(m1->count + m2->count, m1, m2);    (*pSF)[1] = NULL;   /*在 m1、m2 合并后重新排序*/    qsort((*pSF), n, sizeof((*pSF)[0]), SFComp);   }  /* 由建立的 huffman 树对计算每个符号的码字.*/   pSE=(Symbol Encoder*)malloc(sizeof(Symbol Encoder));   memset(pSE, 0, sizeof(Symbol Encoder));   build_symbol_encoder((*p SF)[0], pSE);   return pSE; } 
qsort函数是编译器函数库自带的快速排序函数。函数原型为 void qsort ( void*base , size_t  num , size_t  width , int (_cdecl*compare)(const void*, const void ));

其中base是排序的一个数组,num是指数组中元素的个数,width是指数组中一个元素的长度,compare是自行设计的比较函数。

compare的函数原型为compare( ( void*)&elem1,( void*)&elem2); 当函数小于0时,elem1排在elem2之前,当函数等于0时,elem1等于elem2,当函数大于0时,elem1排在elem2之后。

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

typedef  huffman_code*  Symbol Encoder[MAX_SYMBOLS];  /*256个huffman_code 的指针,位置上对应于 ASCII 的顺序,用于保存码表  */ 
void build_symbol_encoder(huffman_node *subtree, Symbol Encoder *p SF) {   if(subtree == NULL) /*判断是否已到了 root, 是则说明编码过程结束 */    return;   if(subtree->is Leaf) /*是叶结点则产生码字*/    (*pSF)[subtree->symbol] = new_code(subtree);   else   {    build_symbol_encoder(subtree->zero, pSF);   /* 递归,中序遍历*/    build_symbol_encoder(subtree->one, pSF);   } } 
huffman_code* new_code(const huffman_node* leaf) {   /*创建的Huffman码字是从下向上(即从叶到根)编码的,而实际上的码字是从上向下(即从根到叶)编码的*/  unsigned long numbits = 0; /* 码长 */   unsigned char* bits = NULL; /* 码字首地址*/   huffman_code *p;    while(leaf && leaf->parent)   /* leaf !=0: 当前字符存在,应该编码;leaf->parent !=0:当前字符的编码仍未完成,即未完成由叶至根的该字符的编码过程*/   {    huffman_node *parent = leaf->parent;    unsigned char cur_bit = (unsigned char)(numbits % 8); /* 所编位在当前 byte中的位置 */    unsigned long cur_byte = numbits / 8;  /* 当前是第几个 byte */    /*  realloc它与 malloc 不同,它在保持原有的数据不变的情况下重新分配新的空间,原有数据存在新空间中的前面部分(这里空间的地址可能有变化) */    if(cur_bit == 0)    {     size_t new Size = cur_byte + 1;     bits = (unsigned char*)realloc(bits, new Size);     bits[new Size - 1] = 0; /*初始化新分配的 8bit为 0 */    }    /* 先遍历左孩子*/    if(leaf == parent->one)     bits[cur_byte] |= 1 << cur_bit; /* 左移 1 至当前 byte 的当前位(待编位) */    ++numbits;    leaf = parent; /*此时父节点变成了子节点*/  }   if(bits)    reverse_bits(bits, numbits); /* 整个码字逆序,就得到了实际上的Huffman码字了 */   p = (huffman_code*)malloc(sizeof(huffman_code)); 
  p->numbits = numbits;     p->bits = bits; /*  整数个字节。与 numbits 配合才可得到真正码字 */     return p; } 

4)将 Huffman 码表写入文件

for(i = 0; i < MAX_SYMBOLS; ++i)   {    huffman_code *p = (*se)[i];    if(p)    {     unsigned int numbytes;     /* 写符号(1字节)*/     fputc((unsigned char)i, out);     /* 写码字的长度(1字节)*/     fputc(p->numbits, out);     /* 写码字 */     numbytes = numbytes_from_numbits(p->numbits);     if(fwrite(p->bits, 1, numbytes, out) != numbytes)      return 1;    }   } 
5)第二次扫描文件,对文件查表进行 Huffman 编码,并写入文件
int do_file_encode(FILE* in, FILE* out, Symbol Encoder *se) {   unsigned char curbyte = 0;   unsigned char curbit = 0;   int c;   while((c = fgetc(in)) != EOF) /* 遍历文件的每一个字符 */  {    unsigned char uc = (unsigned char)c;    huffman_code *code = (*se)[uc]; /* 查表*/    unsigned long i;    for(i = 0; i < code->numbits; ++i) /* 将码字写入文件 */    {     /* 将当前位添加到当前字节中 */     curbyte |= get_bit(code->bits, i) << curbit;     /*如果当前字节被填满了,那就把它写出来,并重新设置curbyte和curbit*/     if(++curbit == 8)     {      fputc(curbyte, out);      curbyte = 0;      curbit = 0;     }    }   }   /* 如果在curbyte中有尚未输出的数据,这意味着最后编码的字符没有落在字节边界上,则输出curbyte */   if(curbit > 0)    fputc(curbyte, out);   return 0; } 

6)用于统计数据的程序

//step3:add by yzhang for huffman statisticsint huffST_getSymFrequencies(SymbolFrequencies *SF, huffman_stat *st,int total_count){int i,count =0;for(i = 0; i < MAX_SYMBOLS; ++i){if((*SF)[i]){st->freq[i]=(float)(*SF)[i]->count/total_count;count+=(*SF)[i]->count;}else {st->freq[i]= 0;}}if(count==total_count)return 1;elsereturn 0;}int huffST_getcodeword(SymbolEncoder *se, huffman_stat *st){unsigned long i,j;for(i = 0; i < MAX_SYMBOLS; ++i){huffman_code *p = (*se)[i];if(p){unsigned int numbytes;            st->numbits[i] = p->numbits;numbytes = numbytes_from_numbits(p->numbits);for (j=0;j<numbytes;j++)    st->bits[i][j] = p->bits[j];}elsest->numbits[i] =0;}return 0;}void output_huffman_statistics(huffman_stat *st,FILE *out_Table){int i,j;unsigned char c;fprintf(out_Table,"symbol\t   freq\t   codelength\t   code\n");for(i = 0; i < MAX_SYMBOLS; ++i){fprintf(out_Table,"%d\t   ",i);fprintf(out_Table,"%f\t   ",st->freq[i]);fprintf(out_Table,"%d\t    ",st->numbits[i]);if(st->numbits[i]){for(j = 0; j < st->numbits[i]; ++j){c =get_bit(st->bits[i], j);fprintf(out_Table,"%d",c);}}fprintf(out_Table,"\n");}}//end by yzhang/*huffman_encode_file huffman encodes in to out.*/int huffman_encode_file(FILE *in, FILE *out, FILE *out_Table)  //step1:changed by yzhang for huffman statistics from (FILE *in, FILE *out) to (FILE *in, FILE *out, FILE *out_Table){SymbolFrequencies sf;SymbolEncoder *se;huffman_node *root = NULL;int rc;unsigned int symbol_count;    //step2:add by yzhang for huffman statisticshuffman_stat hs;//end by yzhang/* Get the frequency of each symbol in the input file. */symbol_count = get_symbol_frequencies(&sf, in); //演示扫描完一遍文件后,SF指针数组的每个元素的构成//step3:add by yzhang for huffman statistics,...  get the frequency of each symbol         huffST_getSymFrequencies(&sf,&hs,symbol_count);      //end by yzhang/* Build an optimal table from the symbolCount. */se = calculate_huffman_codes(&sf);root = sf[0];//step3:add by yzhang for huffman statistics... output the statistics to filehuffST_getcodeword(se, &hs);output_huffman_statistics(&hs,out_Table);//end by yzhang/* Scan the file again and, using the tablepreviously built, encode it into the output file. */rewind(in);rc = write_code_table(out, se, symbol_count);if(rc == 0)        rc = do_file_encode(in, out, se);/* Free the Huffman tree. */free_huffman_tree(root);free_encoder(se);return rc;}

3  Huffman 解码
1) 解码流程:


2)读取码表并重建据此 Huffman 树

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;   }   /*读取此编码表示的数据字节数 */   if(fread(pDataBytes, sizeof(*pDataBytes), 1, in) != 1)   {    free_huffman_tree(root);    return NULL;   }   /* 读取条目*/   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 tree,当前字节的值在父节点的左右孩子之间切换,根据需要在树中添加新节点 */    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; /*‘1’的一枝的父节点指向当前节点*/     }      p = p->one; /*沿’1’方向下移一级*/    }     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 树的根结点*/ }

3)读取 Huffman 码字,并解码输出
Int huffman_decode_file(FILE *in, FILE *out) {   huffman_node *root, *p;   int c;   unsigned int data_count;   /*读取Huffman码表*/   root = read_code_table(in, &data_count);   if(!root)     return 1; /Huffman 树建立失败*/  /*解码文件. */   p = root;   while(data_count > 0 && (c = fgetc(in)) != EOF) /*data_count >0 :逻辑上仍有数据;(c = fgetc(in)) != EOF):文件中仍有数据*/  {    unsigned char byte = (unsigned char)c; /*1byte的码字*/   unsigned char mask = 1; /* mask用于逐位读出码字*/   while(data_count > 0 && mask) /*loop9: mask = 0x00000000,跳出循环*/   {     p = byte & mask ? p->one : p->zero; /*沿 Huffman 树前进*/    mask <<= 1;    //loop1: byte & 0x00000001                    //loop2: byte & 0x00000010                    //……                    //loop8: byte & 0x10000000     if(p->is Leaf) /*至叶结点(解码完毕)*/    {      fputc(p->symbol, out);      p = root;      --data_count;     }    }   }   free_huffman_tree(root); /*所有 Huffman 码字均已解码输出,文件解码完毕*/  return 0; 
三、实验结果选择十种不同格式类型的文件,使用Huffman编码器进行压缩得到输出的压缩比特流文件。根据得到的编码结果文件,对各种不同格式的文件进行压缩效率的分析。

环境配置及命令行设置如下所示:

(1)下面以表格形式表示的实验结果:

文件类型bmpdocxjpgmp3pdfpngPPTpsdxlsrar平均码长7.3864477.9910197.9205967.9559547.9960657.9304827.6634917.6596814.7712567.999984信源熵(bit/sym)7.3552930977.9850615737.8975167517.93608047.9784562887.906931047.6191211777.630759064.7417977.998687143原文件大小(kB)47633085389476434161860845551495压缩后文件大小(kB)43993085488996535155158263321496压缩比1.08310.9811.0050.9850.9711.0431.0441.6720.999

(2)各样本文件的概率分布图,如下所示:

(3)实验结果的分析。根据(1)和(2)的结果,对各种不同类型文件的统计特性和压缩效率进行分析。

根据无失真编码定理可知,对于编码为二进制码的信源符号来说,平均码长的下界为信源熵。当信源符号分布不均匀时,冗余空间越大,通过霍夫曼编码的效率越高;当信源符号均匀分布时,信源的熵最大,冗余空间最小,此时通过霍夫曼编码的效率最低。初出茅庐,还请各位大神多加指教!
                                             
0 0
原创粉丝点击