[文件压缩]huffman树

目录

压缩原理:

 Huffman树:

 利用huffman进行文件压缩:

压缩:

解压缩:

测试: 

分析:

编写程序中遇到的问题:


在某些时候,我们用U盘拷取大文件的时候,很慢,传输效率不高,我们通常会将文件进行压缩,不仅可以提高传输效率,还可以用更小的空间保存。数据压缩是一门通信原理和计算机科学都会涉及到的学科,在通信原理中,一般称为信源编码,在计算机科学里,一般称为数据压缩。总之,数据压缩是一种很典型且常用的一门技术。而huffman树就是用来压缩数据而创建的一种数据结构.

压缩原理:

倘若文件里面保存AAABCC 等字符,现在我们要对此进行压缩,可以采用什么办法呢?

1.用等长编码代替

因为我们要保存三个字符,而每个字符占用8个比特位;采用2个比特位,我们就可以标识4种状态 00,01,10,11 ;倘若我们用00标识A,01标识B,10标识C的话,上面的文件我们就可以表示000000011010;仅仅占用了12个比特位,当然为了确保我们能够压缩,我们换需要在压缩文件中保存解压缩信息A00 B01 C10 的话,也能够节省大量的空间;

2.不等长编码

以上面为例,我们似乎可以使用不等长编码,0标识A ,10标识B,11标识C ;采用不等长编码,压缩效果会比等长编码的压缩效果更佳明显;但是采用不等长编码的时候,我们需要注意,任何完整编码不能是其余编码的前缀,(比如A的编码是0,B的编码就不能是01 ,因为A编码是B编码的前缀,在解压缩的时候我们无法进行区分); 

总之,我们采用更短的唯一编码来代替原来的字符,以此达到数据压缩的效果。

 Huffman树:

Huffman树可以说就是用来动态生成高效的唯一的不等长编码的数据结构;高效是指,我们希望出现次数越多的字符的编码长度越短越好,因为这样我们可以达到最好的压缩效果;唯一是指任何一个完整编码不是其他任何编码的前缀,我们在解压缩的时候,不会出现无法区分的情况;那么huffman是怎么生成这种编码的呢?

首先,我们需要明确huffman是一棵二叉树;

我们看一个蓝桥杯试题的示例:

对于数列{pi}={5, 3, 8, 2, 9},Huffman树的构造过程如下:
  1. 找到{5, 3, 8, 2, 9}中最小的两个数,分别是2和3,从{pi}中删除它们并将和5加入,得到{5, 8, 9, 5}。
  2. 找到{5, 8, 9, 5}中最小的两个数,分别是5和5,从{pi}中删除它们并将和10加入,得到{8, 9, 10}。
  3. 找到{8, 9, 10}中最小的两个数,分别是8和9,从{pi}中删除它们并将和17加入,得到{10, 17}。
  4. 找到{10, 17}中最小的两个数,分别是10和17,从{pi}中删除它们并将和27加入,得到{27}。
  5.我们就得到了一棵根值为27的huffman树

根据上面的构造过程,我们得到了一棵二叉树,而这个二叉树就被称为huffman树

huffman树的生成过程:

前提:我们构成huffman数的数据全部保存在一个容器内,此时我们可以认为是一个森林

1. 我们取出容器内值最小的两个节点,即容器内不再存在两个值

2.以这两个节点为左右子树构建一个新的二叉树,新二叉树的根节点为这两个节点值之和

3.将这个新的二叉树放入容器

4.重复上述步骤,直到容器内剩余一个二叉树

 现在我们拥有了一棵huffman树,那么它是怎么生成不等长编码的呢?如果我们把左子树路径记为0,右子树路径记为1的话,我们在此看这棵树

如果将路径上的值组合起来形成编码,我们发现数列中的每个数都有一个唯一的编码,并且任何一个完整的编码不会是另一个编码的前缀(这是因为数列中的数全部位于跟节点的位置),即不会出现误判的情况,同时我们观察,数值大的,它的编码长度会小;如果数列中的值代表的就是字符在文件中出现的次数,那么出现次数最多的字符,编码长度一定最短。

也就是说利用huffman树,我们可以生成 可用的不等长编码 用来数据压缩,并且可以达到不错的效果。并且huffman压缩的方法是一种通用的方法,不论什么数据,在计算机中的存储单位都是以字节计算,我们只需要统计每个字节的次数,利用huffman压缩就可以压缩任意类型的数据文件;

也就是给文件中每个字节找一个更短的编码,达到任何文件的通用压缩效果;

 利用huffman进行文件压缩:

压缩:

1.统计每个字节出现的频次

2.利用1中统计的结果,生成huffman树,获取不等长编码;

3.利用2中的不等长编码替换源文件中的每个字节,生成压缩文件

注:为了我们能够解压缩并且接压缩方便,我们需要在压缩文件中保存出现的字节以其频次的信息

解压缩:

1.获取压缩文件中出现的字节以其频次的信息

2.根据1的信息构建huffman树

3.逐步解析压缩文件,得到解压缩文件

举例:

文件a.txt中保存数据 ADDDBBBCCCCDDDDC

我们的压缩文件格式:

在解压缩的时候,我们怎么凭借huffman树和压缩数据信息来还原数据呢?

 

我们在分析压缩数据之前已经构造好了上面的huffman树,在读取压缩数据的时候,我们一个比特一个比特的进行读取,读取到的比特为0,到左子树;为1,到右子树;等我们走到了叶子节点,表示我们解压缩出一个数据,写入解压缩文件;详细过程看下面的图示例程

走到这里,解压缩过程基本已经完成了,但是还存在着一些问题,我们什么时候,解压缩完毕,就不解压缩了呢?

将所有的压缩数据信息全部读取完毕吗?

不知道各位小伙伴有没有注意到:在压缩数据信息中有一部分编码是蓝色的,这是为什么呢?

因此在计算机中字节是最小的存储单位,我们没有办法存储比特位,只能存储字节,所以示例中的压缩数据压缩完毕后的结果应该为1000 0010 1101 1011 1111 1110  0000 0011 ;但是我们在解压缩左后一个字节的时候不知道前面三个0是无效的,可能会解压缩出DDD的源数据,为了避免这种情况,我们将最后一个字节左移3位变为0001 1000;

但是问题又来了?你怎么知道最后3个0是无效的呢?

你仔细观察一下huffman树的根结点;

没错了,根节点的值表示源文件中字节总数,我们每解压缩出一个字节就进行记录,解压缩出根结点的值大小的字节数的时候,我们就解压缩,完毕了。

到这里,我们的huffman压缩文件的所有过程就已经结束了,看到这里的小伙伴可以去尝试一下编写压缩文件的程序:

而我编写的程序连接在下方哦

https://github.com/Qyuan926/FileCompress/tree/master/HuffmanCompress

 

测试: 

下面我们来测试一下压缩文件的压缩效果是怎么样的:

我们这里有五个不同的文件:

运行程序,对上述程序进行压缩,使用Beyond Compare工具进行验证,判断解压缩文件是否和源文件相同

 

五个文件全部解压缩成功,下面我们查看一下压缩文件大小,看一下压缩效果: 

我们发现0、1号文件压缩率为0 ;2号文件压缩率为3.33%; 3号文件压缩率为6.94%;4号文件为28.15%;

分析:

从上面的测试结果,我们发现有的文件压缩率为0 ?那有没有可能压缩文件比源文件还要大呢?

这种情况是某种极端的条件下是存在的,倘若,我们在a.txt中保存一个字符A,那么a.txt的大小就仅占1字节;而我们在压缩文件需要保留文件后缀,这就远远超过了源文件的大小,更不要提及字节以其字节频率信息和压缩数据信息在写入压缩文件;

再者,我们是指字节为单位获取编码进行压缩的,倘若huffman的高度超过8,那么必定有的字符编码可定起不到压缩效果,而且会起反作用;

所以,导致压缩率较低的原因基本就是解压缩信息过大、字符出现次数较均匀,字符种类过多导致树的高度过大;

倘若编码过大的我们不进行压缩,这样的话,我们还需要额外的空间来进行标记那些是压缩的,那些是未压缩的;并且这种编码过大的情况出现次数较少,因此我们不考虑次优化条件;

倘若我们能简化解压缩信息,那么压缩率将会得到提升;

那么究竟能不能简化解压缩信息呢,答案当然是可以的,我们可以采用范式huffman树,这样可以简化解压缩信息,进而提高文件压缩率、与此同时,我们采用范式哈夫曼树,在解压缩的时候,不需要创建huffman树,这也提高了我们程序的运行效率

编写程序中遇到的问题:

1. 解压缩的时候只解压缩了一部分

   注意:我们打开文件的方式 是指文本方式打开,还是以二进制方式打开;

   我们在写入压缩文件的时候,是以字节的方式写入的,如果某个字节是1111 1111即FF,在文本文件中,会被当做文本结束符EOF;当解压缩文件的时候,走到这里,会被认为走到了文件末尾,此次解压缩结束了,而实际上后面还有数据需要进行解压缩;倘若我们以二进制方式进行打开的话,这个问题就不存在了;

2.换行的处理(解压缩阶段)

在上面,我们提到解压缩文件格式的时候,提出 "字符:频次" 的格式,保存解压缩信息;当我们解压缩的时候,只需要每行每行的读,就可以获取的字符一起频次信息了,但是出现了换行符的时候,就会出现错误;

字符占据一行  ":频次"占据一行,与我们设想的情况完全不同,因此我们需要进行特殊处理;

3. 压缩文件中有中文

一个中文可能会占用多个字节,我们在读取数据的时候,读取到的可能是一个负数,倘若一次为下标,存入统计频次的数组中,就会出现数组越界的错误;我们需要将这个读取到的字节转化为无符号类型,在作为下标存入数组;

4.有些解压缩文件中出现部分错误数据

   在我们写入压缩文件的时候,可能最后不到一个字节,我们需要移位,在写入压缩文件,确保压缩文件数据完整。但是由于疏忽,每次分块读取完毕之后,就将本块中不足8位的最后字节、移位写入,导致有些文件解压缩的时候出现错误。

 

注:如果本篇博客有任何错误和建议,欢迎伙伴们留言,你快说句话啊!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值