⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chapter5.htm

📁 数据压缩教程!
💻 HTM
📖 第 1 页 / 共 2 页
字号:
    6     110 10    7     110 11    8    1110 000    9    1110 001</pre><p>其实,如果对 off值考虑其倾向于窗口后部的规律,我们也可以采用变长的编码方法。但这种方式对窗口较小的情况改善并不明显,有时压缩效果还不如固定长编码。</p><p>对三元组的最后一个分量——字符 c,因为其分布并无规律可循,我们只能老老实实地用8 个二进制位对其编码。</p><p>根据上面的叙述,相信你一定也能写出高效的编码和解码程序了。</p><p><strong>另一种输出方式</strong></p><p>LZ77的原始算法采用三元组输出每一个匹配串及其后续字符,即使没有匹配,我们仍然需要输出一个len = 0 的三元组来表示单个字符。试验表明,这种方式对于某些特殊情况(例如同一字符不断重复的情形)有着较好的适应能力。但对于一般数据,我们还可以设计出另外一种更为有效的输出方式:将匹配串和不能匹配的单个字符分别编码、分别输出,输出匹配串时不同时输出后续字符。</p><p>我们将每一个输出分成匹配串和单个字符两种类型,并首先输出一个二进制位对其加以区分。例如,输出0 表示下面是一个匹配串,输出 1表示下面是一个单个字符。</p><p>之后,如果要输出的是单个字符,我们直接输出该字符的字节值,这要用8 个二进制位。也就是说,我们输出一个单个的字符共需要9 个二进制位。</p><p>如果要输出的是匹配串,我们按照前面的方法依次输出off 和 len。对 off,我们可以输出定长编码,也可以输出变长前缀码,对len 我们输出变长前缀码。有时候我们可以对匹配长度加以限制,例如,我们可以限制最少匹配3 个字符。因为,对于 2 个字符的匹配串,我们使用匹配串的方式输出并不一定比我们直接输出2 个单个字符(需要 18位)节省空间(是否节省取决于我们采用何种编码输出off 和 len)。</p><p>这种输出方式的优点是输出单个字符的时候比较节省空间。另外,因为不强求每次都外带一个后续字符,可以适应一些较长匹配的情况。</p><p><strong>如何查找匹配串</strong></p><p>在滑动窗口中查找最长的匹配串,大概是 LZ77算法中的核心问题。容易知道,LZ77算法中空间和时间的消耗集中于对匹配串的查找算法。每次滑动窗口之后,都要进行下一个匹配串的查找,如果查找算法的时间效率在O(n<sup>2</sup>) 或者更高,总的算法时间效率就将达到O(n<sup>3</sup>),这是我们无法容忍的。正常的顺序匹配算法显然无法满足我们的要求。事实上,我们有以下几种可选的方案。</p><p>1、限制可匹配字符串的最大长度(例如 20个字节),将窗口中每一个 20 字节长的串抽取出来,按照大小顺序组织成二叉有序树。在这样的二叉有序树中进行字符串的查找,其效率是很高的。树中每一个节点大小是20(key) + 4(off) + 4(left child) + 4(right child) = 32。树中共有MAX_WND_SIZE - 19 个节点,假如窗口大小为 4096字节,树的大小大约是 130k字节。空间消耗也不算多。这种方法对匹配串长度的限制虽然影响了压缩程序对一些特殊数据(又很长的匹配串)的压缩效果,但就平均性能而言,压缩效果还是不错的。</p><p>2、将窗口中每个长度为 3 (视情况也可取 2 或4)的字符串建立索引,先在此索引中匹配,之后对得出的每个可匹配位置进行顺序查找,直到找到最长匹配字符串。因为长度为3 的字符串可以有 256<sup>3</sup>种情况,我们不可能用静态数组存储该索引结构。使用Hash 表是一个明智的选择。我们可以仅用MAX_WND_SIZE - 1 的数组存储每个索引点,Hash函数的参数当然是字符串本身的 3 个字符值了,Hash函数算法及 Hash之后的散列函数很容易设计。每个索引点之后是该字符串出现的所有位置,我们可以使用单链表来存储每一个位置。值得注意的是,对一些特殊情况比如aaaaaa...之类的连续字串,字符串 aaa有很多连续出现位置,但我们无需对其中的每一个位置都进行匹配,只要对最左边和最右边的位置操作就可以了。解决的办法是在链表节点中纪录相同字符连续出现的长度,对连续的出现位置不再建立新的节点。这种方法可以匹配任意长度的字符串,压缩效果要好一些,但缺点是查找耗时多于第一种方法。</p><p>3、使用字符树( trie )来对窗口内的字符串建立索引,因为字符的取值范围是0 - 255,字符树本身的层次不可能太多,3 - 4层之下就应该换用其他的数据结构例如 Hash表等。这种方法可以作为第二种方法的改进算法出现,可以提高查找速度,但空间的消耗较多。</p><p>如果对窗口中的数据进行索引,就必然带来一个索引位置表示的问题,即我们在索引结构中该往偏移项中存储什么数据:首先,窗口是不断向后滑动的,我们每次将窗口向后滑动一个位置,索引结构就要作相应的更新,我们必须删除那些已经移动出窗口的数据,并增加新的索引信息。其次,窗口不断向后滑动的事实使我们无法用相对窗口左边界的偏移来表示索引位置,因为随着窗口的滑动,每个被索引的字符串相对窗口左边界的位置都在改变,我们无法承担更新所有索引位置的时间消耗。</p><p>解决这一问题的办法是,使用一种可以环形滚动的偏移系统来建立索引,而输出匹配字符串时再将环形偏移还原为相对窗口左边界的真正偏移。让我们用图形来说明,窗口刚刚达到最大时,环形偏移和原始偏移系统相同:</p><pre>偏移:     0 1 2 3 4 ......                                              Max          |--------------------------------------------------------------|环形偏移: 0 1 2 3 4 ......                                              Max</pre><p>窗口向后滑动一个字节后,滑出窗口左端的环形偏移0 被补到了窗口右端:</p><pre>偏移:     0 1 2 3 4 ......                                              Max          |--------------------------------------------------------------|环形偏移: 1 2 3 4 5 ......                                           Max 0</pre><p>窗口再滑动 3 个子节后,偏移系统的情况是:</p><pre>偏移:     0 1 2 3 4 ......                                              Max          |--------------------------------------------------------------|环形偏移: 4 5 6 7 8......                                      Max 0 1 2 3</pre><p>依此类推。</p><p>我们在索引结构中保存环形偏移,但在查找到匹配字符串后,输出的匹配位置off 必须是原始偏移(相对窗口左边),这样才可以保证解码程序的顺利执行。我们用下面的代码将环形偏移还原为原始偏移:</p><pre>// 由环形 off 得到真正的off(相对于窗口左边)// 其中 nLeftOff 为当前与窗口左边对应的环形偏移值int GetRealOff(int off){    if (off &gt;= nLeftOff)        return off - nLeftOff;    else        return (_MAX_WINDOW_SIZE - (nLeftOff - off));}</pre><p>这样,解码程序无需考虑环形偏移系统就可以顺利高速解码了。</p><p><strong>资源</strong></p><p>结合上面的讨论,典型的 LZ77算法应当不难实现,我们本章给出的源码是一个较为特殊的实现。</p><p>示例程序 lz77.exe使用对匹配串和单个字符分类输出的模型,输出匹配串时,off采用定长编码,len 采用γ编码。索引结构采用 2字节长字符串的索引,使用 256 * 256大小的静态数组存储索引点,每个索引点指向一个位置链表。链表节点考虑了对aaaaa... 之类的重复串的优化。</p><p>示例程序的独特之处在于使用了 64k大小的固定长度窗口,窗口不做滑动(因此不需要环形偏移系统,也节省了删除索引点的时间)。压缩函数每次只对最多64k 长的数据进行压缩,主函数将原始文件分成 64k大小的块逐个压缩存储。使用这种方法首先可以增大匹配的概率,字符串可以在64k 空间内任意寻找最大匹配串,以此提高压缩效率。其次,这种方法有利于实现解压缩的同步。也就是说,利用这种方法分块压缩的数据,很容易从原始文件中间的任何一个位置开始解压缩,这尤其适用于全文检索系统中全文信息的保存和随机读取。</p><p>结合上述示例程序,王笨笨开发了可压缩多个文件并可同步(随机)解压缩的文件级接口,但此接口并非自由代码(freecode)。如果需要可以和王笨笨联系。</p><p>示例程序 lz77 的所有源文件被包装在文件 <ahref="src/lz77.zip">lz77.zip</a> 中,由王笨笨编写,在Visual C++ 5.0 环境下编译通过。其使用方法是:</p><p>压缩: lz77 c 源文件名 压缩文件名</p><p>解压缩: lz77 d 压缩文件名 源文件名</p><p> </p><div align="center"><center><address>    <a href="Chapter4.htm">第四章</a> <a href="Chapter6.htm">第六章</a></address></center></div><p align="center"> </p><div align="right"><address>    <a href="mailto:wangyg@contextfree.net">有问题吗?有建议吗?快给王笨笨写信</a></address></div><div align="right"><address>    <strong>章节书签:</strong><a href="default.htm">前言</a>    <a href="content.htm">目录</a> <a href="Chapter1.htm">1</a>    <a href="Chapter2.htm">2</a> <a href="Chapter3.htm">3</a> <a    href="Chapter4.htm">4</a> <a href="Chapter5.htm">5</a> <a    href="Chapter6.htm">6</a> <a href="Chapter7.htm">7</a> <a    href="Chapter8.htm">8</a> <a href="Chapter9.htm">9</a> <a    href="Chapter10.htm">10</a> <a href="Chapter11.htm">11</a> <a    href="Chapter12.htm">12</a> </address></div></body></html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -