一、实验目的
掌握词典编码的基本原理,用C/C++/Python等语言编程实现LZW解码器并分析编解码算法。
二、实验原理
1.LZW概述
- LZW 是一种无损数据压缩算法,对于GIF、TIFF格式等文件以及较大规模的英文文本的压缩具有良好的效果,一般可以压缩到原来大小的一半。
- LZW算法通过建立字典,实现字符重用与编码,适用于source中重复率很高的文本压缩。
2.LZW编码
(1)LZW编码简介
- LZW的编码思想是不断地从字符流中提取新的字符串,通俗地理解为新“词条”,然后用“代号”也就是码字表示这个“词条”。这样一来,对字符流的编码就变成了用码字去替换字符流,生成码字流,从而达到压缩数据的目的
- LZW编码是围绕称为词典的转换表来完成的。LZW编码器通过管理这个词典完成输入与输出之间的转换。LZW编码器的输入是字符流,字符流可以是用8位ASCII字符组成的字符串,而输出是用n位(例如12位)表示的码字流。
(2)具体执行步骤
- 步骤1:将词典初始化为包含所有可能的单字符,当前前缀P初始化为空;
- 步骤2:当前字符C为字符流中的下一个字符;
- 步骤3:判断缀-符串 P+C是否在词典中
(1)如果“是”,P = P+C,返回步骤2
(2)如果“否”
①把代表当前前缀P的码字输出到码字流;
②把缀-符串 P+C添加到词典;
③令P=C(现在的Р仅包含一个字符C)并返回步骤2 - 步骤4:判断码字流中是否还有码字要译
(1)如果“是”,就返回到步骤2;
(2)如果“否”
①把代表当前前缀Р的码字输出到码字流;
②结束。
3.LZW解码
(1)LZW解码简介
LZW解码算法开始时,译码词典和编码词典相同,包含所有可能的前缀根。解码的核心思想在于解码需要还原出编码时的用的字典。要理解解码的原理,首先需要理解它是如何对应编码的过程的。
(2)具体执行步骤
- 步骤1:在开始译码时词典包含所有可能的前缀根(Root)。
- 步骤2:CW表示码字流中的第一个码字。
- 步骤3:输出当前缀-符串 string.cW到码字流。
- 步骤4:先前码字pW : =当前码字cW;当前码字cW : =码字流中的下一个码字。
- 步骤5:判断当前缀-符串 string.cW是否在词典中
(1)如果“是”,则:
①把当前缀-符串string.CW输出到字符流。
②当前前缀P:=先前缀-符串 string.pW。
③当前字符C:=当前前缀-符串 string.cW的第一个字符。
④把缀-符串 P+C添加到词典。
(2)如果“否”,则:
①当前前缀P:=先前缀-符串 string.pW。
②当前字符C:=当前缀-符串 string.W的第一个字符。
③输出缀-符串P+C到字符流,然后把它添加到词典中。 - 步骤6:判断码字流中是否还有码字要译
(1)如果“是”,就返回到步骤4。
(2)如果“否”,结束。
(3)解码过程分析
编码端向解码端传输的是0~255的词典以及需要解码的数字序列,如下图的97 98 98 256 259 99
此时PW=a,CW=b=C,由于a、b均在词典中,执行步骤5(1),将P+C写入词典,即ab写入词典,对应256
由于解码端与编码端相比有延迟,编码时刚加入词典的字符串在解码端立即被用到,故会出现解码时当前码字在词典中不存在的情况。 如果出现当前码字CW在词典中不存在时,该如何解决呢?
首先我们思考对应的LZW编码过程:
- 如图,当p=a,c=b时,P+C=ab在词典中,故有P=P+C=ab,C=a,得P+C=aba不在字典中,故把P+C缀串组合即组成新字符串aba写入词典,以此类推P=C=a,C=b;P+C=ab在字典,此时P=P+C=ab,C=a,P+C=aba在词典,拓展P=aba,C=c,写入P+C=abac于词典。
经过推理可以发现,编码时呈现如下规律:PW的第一个字符即为CW的最后一个字符。
根据编码的规律,解码时先把PW的第一个字符写为新词条的最后一个字符,与PW字符串组合得出新字符串,可推测解码的新字符串是多少并写入词典。
例如下图的解码过程,解码端CW=259尚不存在于字典中,出现无法解码的情况。观察编码规律可推断,259对应的字符串必为上一个字符串PW=ab加上其第一个字符即a,得出CW=aba,此时对259解码得aba并加入词典。
四、实验过程及代码
1、建立头文件及源文件
2、编写程序
(1)bitio.h
/*
* Declaration for bitwise IO
*
* vim: ts=4 sw=4 cindent
*/
#ifndef __BITIO__
#define __BITIO__
#include <stdio.h>
typedef struct{
FILE *fp;
unsigned char mask;
int rack;
}BITFILE;
BITFILE *OpenBitFileInput( char *filename);
BITFILE *OpenBitFileOutput( char *filename);
void CloseBitFileInput( BITFILE *bf);
void CloseBitFileOutput( BITFILE *bf);
int BitInput( BITFILE *bf);
unsigned long BitsInput( BITFILE *bf, int count);
void BitOutput( BITFILE *bf, int bit);
void BitsOutput( BITFILE *bf, unsigned long code, int count);
#endif // __BITIO__
(2)bitio.c
/*
* Definitions for bitwise IO
*
* vim: ts=4 sw=4 cindent
*/
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include "bitio.h"
/*打开二进制输出文件,不存在则新建,存在则覆盖已有文件*/
BITFILE *OpenBitFileInput( char *filename){
BITFILE *bf;
bf = (BITFILE *)malloc( sizeof(BITFILE));
if( NULL == bf) return NULL;
if( NULL == filename) bf->fp = stdin;
else bf->fp = fopen( filename, "rb");//以二进制只写的方式打开文件
if( NULL == bf->fp) return NULL;
bf->mask = 0x80;
bf->rack = 0;
return bf;
}
BITFILE *OpenBitFileOutput( char *filename){
BITFILE *bf;
bf = (BITFILE *)malloc( sizeof(BITFILE));
if( NULL == bf) return NULL;
if( NULL == filename) bf->fp = stdout;
else bf->fp