Huffman编码实现文本文件压缩

整理旧的博客 2009-03-10 21:36:25

这是当初大二时候的课程设计文档

 

PS:很多人留邮箱求源码......囧,好多年前的东西,翻不到了,自个实现一个也不难吧

--------------------------------------------------------------------

辛苦做完了估计老师也不看~~~

貌似这是俺的劳动成果~~~~~O(∩_∩)O~

        

数据结构课程设计报告【一】

课程设计题目:文本文件压缩【一】

 

 题目: 文本文件压缩解压

 需求分析

  输入:文本文件(压缩文件)

  输出:压缩文件(文本文件)  (压缩率)

  知识点:堆排序、霍夫曼树、二叉树遍历、存储数据结构设计

      文件流操作、字符汉字编码方式、二进制文件读写

  备注:字符文件、汉字文件的压缩与解压

 解决问题要点:

1.      文本文件,二进制文件及数据流的操作

2.      英文字符,中文字符的存储与识别【难点】

3.      压缩及解压的主要思想【重点】

4.      Huffman树的构造

5.      编码的获取

6.      压缩文件的存储形式[huffman编码信息及数据存储]

7.      对文本文件最后一个字符的处理[补位数:压缩,解压无错误]

        以上七点即解决问题的关键,仅仅了解整个思想是远远不够的,整个过程中,很多细节需要注意,把握要点的同时,要在具体实现中注意细节。

模块及框架设计:

按功能需求,主要分为五个模块

主要模块:

1.      压缩模块

2.      解压模块

辅助模块:

 1.  字符识别及权重获取

2.  Huffman树构造

3.  获取huffman编码

 

 主要设计及具体实现 【实现代码(带注释)】

压缩解压的实现思想及原理

定义:利用算法将文件有损或无损地处理,以达到保留最多文件信息,而令文件体积变小。

在文本文件中,一个ASCII码占用一个字节空间(1Byte),汉字由两个字节构成(区码和位码),区码和位码分别用ASCII码的161-255字符表示,共94*94=8836个【GB2312编码】

即,无论字符还是汉字,均可由ASCII码表示,我们可以以二进制文件形式将一个文本文件读入,分析每个ASCII码的频数,构造huffman树,并得到相应的编码。

编码是由0、1组成的一串数字,出现频率越高的字符,其编码越短,通过这个特性,我们可以将每8位组成一个新的字符(1Byte),输出到压缩文件中,达到压缩的目的。

例如:

已知  a: 000  b:10  c: 001  d:01   e:11 

如果其中的一段是::abcde 则处理如下:

补上两位

    a     b    c      d     e  

   000   10   00  1  01   11   00

每次取7位【取8位亦可,但此处为了处理最后一个字符,只取7位】

0+7位凑成1Byte,转化为相应AscII码输出

最后一位一般不能刚好凑够8 bit 所以需要补上0

而补上的0的个数记录为补位数 【补位数的处理】

原来:  5*1Byte=8 Byte

处理后:2*1Byte=2 Byte

        仅为原来的四分之一,达到了压缩的目的

 

字符,汉字处理方式

字符,即ASCII码,ASCII编码由8位组成,共256个字符,足够表示英文文本。

汉字,是考虑的重点,多方考虑下,我选择了系统支持的GB2312编码表,而非转化为UNICODE编码。GB2312的汉字编码占用两个字节,区码和位码分别由ASCII码表的161-255字符表示,共8836个汉字,足以承担汉字处理,而UNICODE编码表是变长编码,处理困难且不利于压缩。

汉字ASCII码八位1*******   一般字符编码八位0*******

    处理的思想:

以字符形式读入,每次读入一个字节(1Byte)进行处理,不去判断区分是字符还是汉字,只识别ASCII码,进行处理。

 

设计字符所存储的数据结构

读入的字符所需要存储的信息:字符,频数以及各自编码

仅仅以char数组存储将带来非常大的缺陷。

 

作用:提高效率,减小复杂性

 class huffmanchar

{

char c;//字符

int count;//字符出现的次数

intbit[20];//用于存储huffman编码                                                                                                                                                                                                                                                                                                                                                                                  

int codelength;//bit中编码的长度

}

  实例化   huffmanchar huff[256]; //ASCII字符总数

           Int  totalcount;//记录字符总数 ,

   

 

设计树节点的数据结构

在压缩时,为了解压时能够顺利获取该文件相应的huffman编码,我们可以设计一个表头,以存储编码信息,但是这种形式不但造成读入困难,而且占用空间巨大。

解决方法:压缩时,将整棵huffman以对象的形式写入压缩文件开头,解压整棵读出,强制类型转换即可方便地得到huffman树

class BinaryTreeNode 

{

    Int  data;//频数,即权重

char character;//相应字符

BinaryTreeNode<T> *LeftChild,*RightChild;

};

 

设计Huffman树的数据结构

Huffman具有一般二叉树的所有特性,但是,在压缩处理过程中遇到一个情况,我们无法正确处理最后一个字符,因为通常到最后一位,编码不会满8位,这就在解压时输出多余字符。

解决方法,每次令始终第一位为0,仅取7位编码:0*******

处理到最后一位时,首位为1,并记录缺位数lackcount

Lackcount传入huffman树对象,以写入压缩文件

class BinaryTree 

{

   BinaryTree(){ root=0;lackcount=0;}

BinaryTreeNode<T> *root;

   int lackcount;//构造对象是用于存储最后一位的缺位数~~~~~

}

 

 

 

 

整体构架及处理流程:

     主类:Oper.h   字符获取,文件读取,流处理,压缩,解压缩

辅助类:BinaryTree.h  构造huffman树及获取相应编码

             MinHeap.h   用于辅助构造huffman树

          Chain.h      存储遍历huffman时产生的编码

          BinaryTreeNode.h  存储huffman树结点

          Huffmanchar.h     存储字符及权重,编码

          Huffman.h        存储huffman

          OutOfBounds.h    huffman结点,存储树及权重

 

 

压缩主要流程:

   

读入文件       #include <fstream>

                        Ifstream input;

                        Input.open(“d://test.txt”);

 

 

计算频数     huffmanchar huff[256];//字符数组

            Int  totalcount;//记录字符总数

                 辅助函数

                 bool Oper::judge(int c)//判断c所对应字符是否在数组里出现

                  //若是出现过,该字符count++;

                 void Oper::add(char c)//添加新出现的字符totalcount++;

 

                   主函数:

                        void Oper::getFrequency()//计算频数

                         {

                             while(!input.eof())

                             {

                          input.get(temp);

                             if(!judge((int)temp))

                                    add(temp); 

                               }

                          }

构造huffman树

                   构造霍夫曼树

 思想:

1.为了构造霍夫曼树,首先从仅含一个外部节点的二叉树集合开始,每个外部节点代表字符串中一个不同的字符,其权重等于该字符的频率。

2.此后不断地从集合中选择两棵具有最小权重的二叉树,并把它们合并成一棵新的二叉树,合并方法是把这两棵二叉树分别作为左右子树,然后增加一个新的根节点。新二叉树的权重为两棵子树的权重之和。

3.这个过程可一直持续到仅剩下一棵树为止。

  

实现:               

 利用最小堆,将每个权重作为一颗仅有根节点的树传入,每次取出权值最小两个,组成一颗新树并放回到最小堆,直到仅剩一棵树

                    

                                  BinaryTree<int> HuffmanTree(int a[],char c[],int n)

                                    {

                                    Huffman<int> *w=new Huffman<int>[n+1];

                                    BinaryTree<int> z,zero;

  

                                    MinHeap<Huffman<T> > H(1);//实例化最小堆对象

                                    H.Initialize(w,n,n);

                                    Huffman<int> x,y;

                                    for(i=1;i<n;i++)

                                    {

                                          H.DeleteMin(x);//每次取权值最小两个

                                          H.DeleteMin(y);

                                          z.MakeTree(0,' ',x.tree,y.tree);//组成新树

                                          z.weight=x.weight+y.weight;

                                          H.Insert(z);//再次加入

                                    }

                                         return x.tree;//最终返回即huffman树

                                    }

 

获取huffman编码

                 伪代码

                       Chain  A;//由于存储链表huffman编码

                void OutputCode(root)

                 {

                    if(t->LeftChild ) //若是左子树不为空

                   {

                     往左走,0加入链表

                     OutputCode(root->leftChild)//递归处理

                    }

                       if(t->RightChild)

                    {

                       往右走,1加入链表

                         OutputCode(root->rightChild)//递归处理

                     }

                    if(t->LeftChild==NULL && t->RightChild==NULL)

                      {

                        若为叶节点,将链表内编码传到相应字符的bit数组

                        删除链表最后一个节点

                         Return;

                      }

                         Return;

                        }

 

再次读入文件

压缩模块开始        

主要思想:

    将源文件字符一个一个读入,判断,寻找其相应编码

                      将编码一位一位加入缓冲区

                      缓冲区满8位时,转化为相应的形影ASCII编码

                      将编码输出

 

BinaryTree<int>& bc;//将树对象先写到文件头

  output.write((char *)&bc,sizeof(bc));

 

                     int buffer[8];//用作缓冲区~~~

                     int bufcount;//记录缓冲字节数~~,初始为1

                     主函数:

void encoding(BinaryTree<int>& bc)

//调用辅助函数

//读入文件,处理每个字符,得到其编码

//将编码加入缓冲区

                     辅助函数:

int changeinto(int *a)

//缓冲区满时调用,将8位数组转化为一个int

void addToBuff(int a,ofstream output)

//将编码加入缓冲函数~~~

//缓冲区满时,转化为一个int ,写出output<<(char)intA void flushbuffer(ofstream output)

//处理完最后一位字符后,缓冲区存在编码

//将首位设为1,将缓冲区剩余的输出

   

压缩结束

 

 

 

解压流程:

        

读入文件 

 

重构Huffman树

                        直接将其存于压缩文件表头的对象读出

                             BinaryTree<int> bc;

                             input.read((char *)&bc,sizeof(bc));  

                              并得到编码的缺位数

                               Lackcount=bc.lackcount;

 

开始解压 

         解压思想:

1. 每次一个Byte ,

                2. 转化为相应二进制串,识别首位是否

                3. 开始遍历huffman树(0左  1右)

                   若为叶节点,输出相应字符

指针回到根节点,重复此过程  

void Oper::gothrough(BinaryTree<int> bc,int a,ofstream output)

//遍历树,若是叶节点输出~~~~

{             

       if(a==0 && tnode->LeftChild )

{

            tnode=tnode->LeftChild;  

       }

       else if(a==1 && tnode->RightChild)

{

            tnode=tnode->RightChild;

       }

if(tnode->LeftChild==NULL && tnode->RightChild==N

解压结束           {

                   output<<tnode->character;

                   output.flush();

         tnode=bc.root;    

       }  

 压缩率:

 

表头Bytes+字符编码长(codelength)总和/8

           总字符数Bytes

 

测试:

操作界面

 

 

 

 

 

 

测试文件:

l  小文件

1. 英文文本: 

test1.txt    《I have a dream》全文   

压缩前:8.84KB      

压缩后:5.51KB

压缩率:63%

测试结果:

压缩,解压成功!压缩效果不错

内容与原文件相同

英文压缩测试成功

 

2.中文文本:

test2.txt    《GB2312编码表》   

压缩前:15.4KB

压缩后:11.6KB

压缩率:75%

测试结果:压缩,解压成功!

内容于原文件相同,无乱码

中文汉字测试成功

 

l  略大文件  

test3.txt   《平凡的世界》       

压缩前:1.62M

压缩后:1.39M

压缩率:86%

压缩时间14.23秒

解压时间 16.85秒

测试结果:压缩,解压成功!

          压缩解压时间在可接受范围之内

 

----------------------------------------------------------

当初貌似写得挺详细

整理的时候看了下,其实

每次取7位【取8位亦可,但此处为了处理最后一个字符,只取7位】

这里就是改进的地方,因为最后一个字符的处理,导致了前面每次只取七位,造成极大浪费

改进处理:每次取8位,最后一个字符另存

你可能感兴趣的:(数据结构,测试,存储,Class,byte,output)