免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2085 | 回复: 0
打印 上一主题 下一主题

lzw 压缩算法的原理与细节思考【转】 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-12-03 23:19 |只看该作者 |倒序浏览

lzw 是一种无损数据压缩算法。
lzw 压缩原理:
为了简化问题,下面用的是伪代码:
1.首先初始化一个“字典”,“字典”里包含了 128 个 ASC II 码。
    var dictionary = new Array;
    for(i = 0; i < 128; i++)
    {
      dictionary=String.fromCharCode(i);
    }
2.不断地在输入文件中寻找在字典中出现的最长的匹配p,并输出其在字典中的位置值到目的文件。若输入文件中下一个字符为c,把pc插入字典。
  
    StringInDictionary = input_first_char();
    while( ! AtEndOfFile )
    {
      if( search_dictionary(StringInDictionary) ) != null)
      {
          CodeInDictionary = search_dictionary(StringInDictionary);
          NextChar = input_next_char();
          StringInDictionary += NextChar;
      }
      else
      {
          Output(CodeInDictionary);
          dictionary[dictionary.length] = StringInDictionary;
          StringInDictionary = NextChar;
      }
    }
    /*在字典里搜索特定字符串*/
    function search_dictionary(str)
    {
      for( i = 0; i < dictionary.length; i ++ )
      {
          if( dictionary == str )
            return i;
      }
      return null;
    }
这样就得到了压缩文件。
可以看出,压缩文件里并没有包含字典,事实上,解压缩时字典是可以根据压缩文件里的内容重建的。
下面我们来看一下解压缩的代码:
    var dictionary = new Array;
    for(i = 0; i < 128; i++)
    {
      dictionary = String.fromCharCode(i);
    }
    previous_code = ReadFirstCode();
    OutPutString = dictionary[previous_code];
    Output(OutPutString);
    while( ! AtEndOfFile )
    {
      current_code = ReadNextCode();
      OutPutString = dictionary[current_code];
      Output(OutPutString);
      dictionary[dictionary.length] = dictionary[previous_code] + OutPutString.substr(0, 1);
      previous_code = current_code;
    }
可以用这个原理来压缩任何文件,gif 也是基于这样的原理,只不过字典初始化时不是存储 ASC II 码,而是 256 种(或更少)预定义的颜色值。对于通用文件压缩,字典初始化时存储一个字节的所有可能的取值(0 到 255)。
一些细节问题有
1.字典数组的长度我们限制在 4096,这样,字典的每个位置值,我们用 12 位的二进制整数就足够表示了,输出时,我们把 4 个位置值(48 位)拼凑成 3 个 unicode 字符(正好也是 48 位)输出。
2.压缩和解压缩时,有可能最后一个代码没有被输出,编程时需要注意,具体解决可以看我在前面发的完整程序。
3. 解压缩时,有可能某些代码在字典中找不到对应的解压文本,通过仔细考察压缩过程,可以知道这个代码对应的文本是dictionary [previous_code] + dictionary[previous_code].substr(0, 1)。其中 previous_code 是此代码的前一个代码。
下面我们来看一个棘手的问题:
字典应该用什么样的数据结构来组织,以提高查找匹配串的效率。
为什么用哈希表来组织字典能有效减少程序的运算量,使搜索字典的速度比遍历普通一维数组提高几千倍。
首先我们考察一下字典需要存储哪些内容:
1.前面我们知道字典需要存储 4096 个字符串(key),内容因输入文件的不同而无法预知。
2.字典还需要存储这些字符串相对应的编号(code),内容是 0 到 4095。
最直观和最容易想到的是一维数组,像这样:
dictionary[code] = key;
这样的数组只能通过遍历来搜索一个特定的 key,最坏的情况是 4096 个循环,考虑到输入文件内容的随机性,搜索一个 key 平均要循环两千多次。
那么哈希表是怎么做的呢?
哈希表首先创建一些“桶”,再把元素分散到一个个的“桶”里,根据元素的 key(而不是 code),就可以确定这个元素是在哪一个“桶”里,然后去遍历这个“桶”。
其中的关键是把元素分散到特定“桶”里的规则,其实,这个规则是由你自己定义的,一个好的规则应该是:根据 key 能够确定唯一的“桶”;分配尽可能做到均匀,能确实降低元素的密度(单个“桶”里的元素尽可能少);规则的算法尽可能简单,运算量越少越好。这个规则被称为哈希函数。
在我们的程序里,哈希表初始化时创建了 4099 个“桶”,采用的哈希函数是:keyword % 4099
其中 keyword 是由 key 所包含的单个字符的 ASC II 编码值拼接而成。
由于字典里只要存储 4096 个元素,所以“桶”里的元素的平均密度小于 1。搜索一个 key 最坏的情况仍然只是 4096 个循环(出现这种情况几乎不可能),而平均的循环次数降低到 1 次。
我们的“桶”是子数组,4099 个“桶”构成的二维数组就是我们的哈希表。
总结:hash 是分散的意思,哈希表又称为散列,它的实质是把元素按照自定的规则分散开存储,以有效降低搜索的密度。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/107284/showart_2110658.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP