数据压缩实验3-Huffman编码

一、实验原理

1.Huffman 编码

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

变字长编码(VLC)的一种。

2)        2 Huffman 编码基于信源的概率统计模型,它的基本思路是,出现概率大的信源符

号编长码,出现概率小的信源符号编短码,从而使平均码长最小。

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

即时码。

2.Huffman 编码的方法

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

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

c)        每一次选出最小的两个值,作为二叉树的两个叶子节点,将和作为它们的根节点,

这两个叶子节点不再参与比较,新的根节点参与比较;

d)        重复上一步,直到最后得到和为 1 的根节点;

e)        将形成的二叉树的左节点标 0,右节点标 1,把从最上面的根节点到最下面的叶

子节点途中遇到的 0,1 序列串起来,就得到了各个符号的编码。

举例如下:


二、实验过程

1.      实验流程


2.      Huffman编码的数据结构

2.1节点:

通过Huffman节点建立Huffman树。

typedef structhuffman_node_tag

{

      unsigned char isLeaf;               //是否为树叶

      unsigned long count;                  //节点代表的符号加权和

      struct huffman_node_tag *parent;             //父节点指针

union

      {

               struct

               {

                struct huffman_node_tag *zero, *one;  

           //子节点指针,分别代表0,1子节点指针

               };

               unsigned char symbol;             //节点代表的符号

      };

} huffman_node;

2.2  Huffman码结构

typedef struct huffman_code_tag

{

unsigned longnumbits;            //该码所用的比特数

unsigned char*bits;          //指向该码比特串的指针

/*码字的第 1 位存于 bits[0]的第 1 位,

码字的第 2 位存于 bits[0]的第的第 2 位,

码字的第 8 位存于 bits[0]的第的第 8 位,

码字的第 9 位存于 bits[1]的第的第 1 位*/

} huffman_code;

三、实验代码分析

1从指定文件中读取数据,统计每个符号发生的概率,并建立相应的树叶节点

//设置符号概率统计结果输出

#define MAX_SYMBOLS 256

//256个信源符号的Huffman节点

typedef huffman_node*SymbolFrequencies[MAX_SYMBOLS];

//256个Huffman节点的编码符号,即码表

typedef huffman_code*SymbolEncoder[MAX_SYMBOLS];

//----------------以下结构体用于保存符号频率统计结果-----------------------//

typedef struct huffman_statistics_result

{

/*创建一个 256 个元素的指针数组,用以保存 256 个信源符号的频率*/

         floatfreq[256];

/*创建一个 256 个元素的指针数组,用以保存 256 个信源符号的所用比特长度*/

         unsignedlong numbits[256];

  /*将每个信源符号与其使用比特数相对应起来*/

         unsignedchar bits[256][100];

}huffman_stat;

// 在统计所有信源符号前将结果结构初始化,开辟空间

huffman_stat *init_huffstatistics()

{        huffman_stat*p;

int i;

         p= (huffman_stat*)malloc(sizeof(huffman_stat));

         p->freq= (float *)malloc(sizeof(float)*256 );

         p->numbits= (unsigned long *) malloc(sizeof(unsigned long)*256);

   for (i=0 ; i<256;i++)

                   p->bits[i]= (unsigned char *)malloc(sizeof(unsigned char)*100);

         returnp;

}

//----------------------------------------------------------------------------------------------//

 

/*统计信源符号发生频次并记录的函数*/

static unsigned int

get_symbol_frequencies(SymbolFrequencies*pSF, FILE *in)

{

         intc;

         unsignedint total_count = 0;

        

         /*初始化所有频率都为0. */

         init_frequencies(pSF);

        

         /*第一次扫描,从输入文件中统计每个信源符号发生概率 */

         while((c= fgetc(in)) != EOF)

         {

                   unsignedchar uc = c;

       // 如果是一个新符号,则产生该字符的一个新叶节点

                   if(!(*pSF)[uc])

                            (*pSF)[uc]= new_leaf_node(uc);

                   ++(*pSF)[uc]->count;   //当前字符出现的频数+1

                   ++total_count;     //总信源符号数 +1

         }

 

         returntotal_count;

}

2   按频率从小到大的顺序排序并建立Huffman树

static SymbolEncoder*

calculate_huffman_codes(SymbolFrequencies *pSF)

{

         unsignedint i = 0;

         unsignedint n = 0;

         huffman_node*m1 = NULL, *m2 = NULL;

         SymbolEncoder*pSE = NULL;

#if 1

         printf("BEFORESORT\n");

         print_freqs(pSF);  

#endif

 

         /*按信源符号出现频率从小到大排序.*/

         qsort((*pSF),MAX_SYMBOLS, sizeof((*pSF)[0]), SFComp);  

#if 1

         printf("AFTERSORT\n");

         print_freqs(pSF);

#endif

 

         /*得到所出现的信源符号的总数为n */

         for(n= 0; n < MAX_SYMBOLS && (*pSF)[n]; ++n)

                   ;

 

         /* 建立 huffman 树,将节点合并 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= (SymbolEncoder*)malloc(sizeof(SymbolEncoder));

         memset(pSE,0, sizeof(SymbolEncoder));

         build_symbol_encoder((*pSF)[0],pSE);

         returnpSE;

}

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

//建立新的树叶节点

static huffman_node*

new_leaf_node(unsigned char symbol)

{

         huffman_node*p = (huffman_node*)malloc(sizeof(huffman_node));

         p->isLeaf= 1;

         p->symbol= symbol;

         p->count= 0;

         p->parent= 0;

         returnp;

}

//建立新的非树叶节点

static huffman_node*

new_nonleaf_node(unsigned long count,huffman_node *zero, huffman_node *one)

{

         huffman_node*p = (huffman_node*)malloc(sizeof(huffman_node));

         p->isLeaf= 0;

         p->count= count;

         p->zero= zero;

         p->one= one;

         p->parent= 0;

         returnp;

}

//遍历Huffman树,得到每个已存在信源符号的码字,形成码表

static void

build_symbol_encoder(huffman_node *subtree,SymbolEncoder *pSF)

{

         if(subtree== NULL)

                   return;//根节点则返回

         if(subtree->isLeaf)

                   (*pSF)[subtree->symbol]= new_code(subtree); //若为树叶,则构造霍夫曼码结构

         else

         {   否则递归这个函数,直到达到树叶节点

                   build_symbol_encoder(subtree->zero,pSF);

                   build_symbol_encoder(subtree->one,pSF);

         }

}

static huffman_code*

new_code(const huffman_node* leaf)

{

         /*从树叶节点至根节点进行码符号查询并反向排列得到码字 */

         unsignedlong numbits = 0;//码长

         unsignedchar* bits = NULL;//码字首地址

         huffman_code*p;

//找到树叶节点后,向上遍历,顺序写入码串

         while(leaf&& leaf->parent)//节点及父节点是否存在

         {

                   huffman_node*parent = leaf->parent;

// 所编位在当前 byte中的位置

                   unsignedchar cur_bit = (unsigned char)(numbits % 8);

                   unsignedlong cur_byte = numbits / 8;   // 当前byte数

 

                   /* realloc与 malloc 不同,它在保持原有的数据不变的情况下重新分配新的空间,原有数据存在新空间中的前面部分*/

                   if(cur_bit== 0)             // 开辟一个字节来存储码字

                   {

                            size_tnewSize = cur_byte + 1;

                            bits= (char*)realloc(bits, newSize);

                            bits[newSize- 1] = 0; /* Initialize the new byte. */

                   }

 

                   if(leaf== parent->one) //若为1节点,则将对应位置1

                            bits[cur_byte]|= 1 << cur_bit;

 

                   ++numbits;//码位数加一

                   leaf= parent;//将其置为父节点

         }

         if(bits)

                  reverse_bits(bits, numbits);//实现码字逆序排列

 

         p= (huffman_code*)malloc(sizeof(huffman_code));

         p->numbits= numbits;

         p->bits= bits;

         returnp;

}

 

4.将 Huffman 码表写入文件

static int

write_code_table(FILE* out, SymbolEncoder*se, unsigned int symbol_count)

{

         //……省略部分代码

         /*将码表写入文件*/

         for(i= 0; i < MAX_SYMBOLS; ++i)

         {

                   huffman_code*p = (*se)[i];

                   if(p)

                   {

                            unsignedint numbytes;

                            /*Write the 1 byte symbol. */

                            fputc((unsignedchar)i, out);

                            /*Write the 1 byte code bit length. */

                            fputc(p->numbits,out);

                            /*Write the code bytes. */

                            numbytes= numbytes_from_numbits(p->numbits);

                            if(fwrite(p->bits,1, numbytes, out) != numbytes)

                                     return1;

                   }

         }

         return0;

}

 

/*其他码表分析函数*/

//----------得到码字长度与所占权重--------//

int huffST_getSymFrequencies(SymbolFrequencies*SF, huffman_stat *st,int total_count)

{

         inti,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)

                   return1;

         else

                   return0;

}

//--------得到码字与码长-------//

int huffST_getcodeword(SymbolEncoder *se,huffman_stat *st)

{

         unsignedlong i,j;

 

         for(i= 0; i < MAX_SYMBOLS; ++i)

         {

                   huffman_code*p = (*se)[i];

                   if(p)

                   {

                            unsignedint 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];

                   }

                   else

                            st->numbits[i]=0;

         }

 

         return0;

}

//-----------------以表格形式输出码表------------------//

void output_huffman_statistics(huffman_stat*st,FILE *out_Table)

{

         inti,j;

         unsignedchar 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");

         }

}

5. 编码

int

huffman_encode_file(FILE *in, FILE *out,FILE *out_Table)  //可以表格形式输出结果

{

         SymbolFrequenciessf;

         SymbolEncoder*se;

         huffman_node*root = NULL;

         intrc;

         unsignedint symbol_count;

    /*得到每一个信源符号发生概率并以表格形式输出统计结果*/

         huffman_staths;

         symbol_count= get_symbol_frequencies(&sf, in);

   huffST_getSymFrequencies(&sf,&hs,symbol_count);

         /*Build an optimal table from the symbolCount. */

         se= calculate_huffman_codes(&sf);

         root= sf[0];

   

         //获取码表并输出

         huffST_getcodeword(se,&hs);

         output_huffman_statistics(&hs,out_Table);

        

 

         /*第二次扫描文件,从输入文件中遍历每一个输入符号

查询码表进行编码,并将码字写入文件,并以表格形式输出实验结果*/

         rewind(in);

         rc= write_code_table(out, se, symbol_count);

         if(rc== 0)

                   rc= do_file_encode(in, out, se);

 

         /*清空Huffman树. */

         free_huffman_tree(root);

         free_encoder(se);

         returnrc;

}

 

 

主函数:

int

main(int argc, char** argv)

{

         charmemory = 0;

         charcompress = 1;

         intopt;

         constchar *file_in = NULL, *file_out = NULL;

         //统计数据表格指针

         constchar *file_out_table = NULL;

         FILE*in = stdin;

         FILE*out = stdout;

         FILE* outTable = NULL;

 

         /*获取命令行参数 */

         while((opt= getopt(argc, argv, "i:o:cdhvmt:")) != -1) 

         {

                   switch(opt)

                   {

                   case'i':

                            file_in= optarg;

                            break;

                   case'o':

                            file_out= optarg;

                            break;

                   case'c':

                            compress= 1;

                            break;

                   case'd':

                            compress= 0;

                            break;

                   case'h':

                            usage(stdout);

                            return0;

                   case'v':

                            version(stdout);

                            return0;

                   case'm':

                            memory= 1;

                            break;

                   //生成数据统计表格

                   case't':

                            file_out_table= optarg;                    

                            break;

                  

                   default:

                            usage(stderr);

                            return1;

                   }

         }

 

         //…..省略部分代码

}

四、实验结果及分析

将10种不同类型的文件直接进行Huffman编码,统计各个文件符号概率分布










统计各个文件平均码长以及压缩比:


由于各个文件符号概率都比较平均,Huffman编码并没有带来较高的压缩率,

Huffman编码对符号概率相差比较大的信源压缩效率更好。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值