Lab3 LZW 编解码算法实现与分析——C语言代码实现
一、实验步骤
- 首先调试LZW的编码程序,以一个文本文件作为输入,得到输出的LZW编码文件。
- 将得到的编码文件作为输入文件,编写LZW的解码程序。
- 选择至少十种不同格式类型的文件,使用LZW编码器进行压缩得到输出的压缩比特流文件,并对各种不同格式的文件进行压缩效率的分析。
二、实验原理
(一)编码原理
LZW算法编码基本思想:
- 初始化包含单字符的词典表
- 判断输入数据流中的当前字符串是否在词典表中
- 若在词典中,则继续读取输入数据
- 若不在词典中,则将当前字符串写入词典,分配一个新的数字索引号,生成新词条,便于下次使用
- 将新写入的索引号进行输出,实现压缩目的,并继续读取输入数据
- 循环判断,直至输入数据全部读取完毕
数据的重复性是算法实现的保证之一。
(二)解码原理
LZW算法解码基本思想:
- 初始化包含单字符的词典表
- 判断输入数据流中的当前字符串是否在词典表中
- 若在词典中,则把当前字符串输出到字符流,并将先前字符串+当前字符串的第一个字符写入词典中
- 若不在词典中,则把先前字符串+当前字符串的第一个字符进行输出,并将其写入词典中
- 继续读取输入数据,循环判断,直至输入数据全部读取完毕
需要特别注意的是:
对于步骤6进行判断的第二种情况,即当前前缀-符串string.CW并不在词典中的情况,我们依然可以正确实现解码输出。
- 出现这种情况的原因:在编码时,上一个刚创建的新词条直接被下一个词组所使用。
- 算法的实现表明,解码端的词典建立会比编码端晚一步。
- 处理这种情况的做法:由于下一个词条的尾缀必定是该词条的第一个字符,所以直接输出这个字符,然后再用先前一条词条的code进行译码即可。
三、程序实现
(一)数据结构分析
- 树用数组dict[ ]表示,数组下标用pointer表示
- dict[pointer]表示一个节点
- dict[pointer].suffix表示尾缀字符
- dict[pointer].parent表示母节点
- dict[pointer].firstchild表示第一个孩子节点
- dict[pointer].nextsibling表示下一个兄弟节点
(二)主要模块分析
1、初始化词典表
void InitDictionary( void){
int i;
for( i=0; i<256; i++){
dictionary[i].suffix = i;
dictionary[i].parent = -1;
dictionary[i].firstchild = -1;
dictionary[i].nextsibling = i+1;
}
dictionary[255].nextsibling = -1;
next_code = 256;
}
2、查找字符串
int InDictionary( int character, int string_code){
int sibling;
if( 0>string_code) return character;
sibling = dictionary[string_code].firstchild;
while( -1<sibling){
if( character == dictionary[sibling].suffix) return sibling;
sibling = dictionary[sibling].nextsibling;
}
return -1;
}
3、在词典中写入新词条
4、编码实现函数
5、解码实现函数
void LZWDecode( BITFILE *bf, FILE *fp)
{ //解码端实现函数
int character; //字符代号
int new_code, last_code;
int phrase_length; //字符串长度
unsigned long file_length; //文件长度
file_length = BitsInput( bf, 4*8); //根据传入参数bf计算有多少个字符
if( -1 == file_length) file_length = 0;
InitDictionary(); //初始化字典,为每个ASCII字符对应一个词条,即0-255个词条
last_code = -1;
while( 0<file_length) //判断输入的数据流是否已经全部读取完毕
{
new_code = input( bf); //读取一个输入的索引号
if( new_code >= next_code) //将new_code与next_code进行比较,比较当前读取的索引号与词典目前最大索引号的大小关系
//判断解码端收到的码字是否已经存在于字典中
{ // this is the case CSCSC( not in dict) 解码端收到的码字不在字典中
d_stack[0] = character; //将当前字符代号写入堆栈,即当前字符串的尾缀更新为当前字符
phrase_length = DecodeString( 1, last_code); //解码得到字符
}
else
{// 解码端收到的码字已经在字典中
phrase_length = DecodeString( 0, new_code); //解码得到字符
}
character = d_stack[phrase_length-1]; //更新堆栈,倒序存储,为写入新词条做准备
while( 0<phrase_length) //将字符串写入文本文件
{
phrase_length --;
fputc( d_stack[ phrase_length], fp);
file_length--;
}
if( MAX_CODE>next_code) //当词典还有可写入新词条的内存空间时
{// add the new phrase to dictionary
AddToDictionary( character, last_code);
}
last_code = new_code; //更新词典词条总数
}
}
(三)实验结果
原文本文件lab6_test.txt
编码输出的压缩文件lab6_encode.txt
解码得到的文本文件lab6_decode.txt
由实验结果可知,编解码过程均正确。
四、压缩效率对比
选取十种不同格式类型的文件,使用LZW编码器进行压缩,计算压缩效率。
文件格式 | 压缩效率 |
---|---|
1.txt | 0.6 |
2.pdf | 1.296 |
3.xlsx | 1.255 |
4.docx | 1.615 |
5.bmp | 0.272 |
6.jpg | 1.192 |
7.png | 1.267 |
8.avi | 1.194 |
9.mp4 | 1.237 |
10.html | 0.559 |
- 总结:
- LZW算法适合用于压缩较大的文字类型文件,且内容重复率越高,压缩效率越大。
- LZW算法的实现基础在于编解码两端词典表的创建。
- LZW算法不需要将词典表传送至解码端,从而避免了因词典表的内容过大,导致压缩失效。