原理
LZW的思想通过一系列编号来代替短语,再次遇到此短语是用编号来替代,以此来达到压缩的目的。由于词典的构建过程固定故不需要传词典,解决了码表传输的问题。解码时在解码端重建词典即可进行解码。
编码
LZW编码算法的步骤如下:
步骤1:将词典初始化为包含所有可能的单字符,当前前缀P初始化为空。
步骤2:当前字符C=字符流中的下一个字符。
步骤3:判断P+C是否在词典中
(1)如果“是”,则用C扩展P,即让P=P+C,返回到步骤2。
(2)如果“否”,则
- 输出与当前前缀P相对应的码字W;
- 将P+C添加到词典中;
- 令P=C,并返回到步骤2
由于个人水平问题,这次编码的代码使用的是老师已经给出的,包括位输入输出工具及各子函数,不做展示,每个函数的作用及返回值会在解码的代码里中标注
解码
解码的步骤如下:
-
在开始译码时词典包含所有可能的前缀根;
-
步骤2:令CW:=码字流中的第一个码字;
-
步骤3:输出当前缀-符串string.CW到码字流;
-
步骤4:先前码字PW:=当前码字CW;
-
步骤5:当前码字CW:=码字流的下一个码字;
-
步骤6:判断当前缀-符串string.CW 是否在词典中;
(1)如果”是”,则把当前缀-符串string.CW输出到字符流;
当前前缀P:=先前缀-符串string.PW;
当前字符C:=当前前缀-符串string.CW的第一个字符;
把缀-符串P+C添加到词典;
(2)如果”否”,则当前前缀P:=先前缀-符串string.PW。
当前字符C:=当前缀-符串string.CW的第一个字符。
输出缀-符串P+C到字符流,然后把它添加到词典中。7.步骤7:判断码字流中是否还有码字要译。
(1)如果”是”,就返回步骤4。
(2)如果”否”,结束。
代码如下
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include "bitio.h"
#define MAX_CODE 65535
struct {
int suffix;
int parent, firstchild, nextsibling;
} dictionary[MAX_CODE+1];//字典
int next_code;
int d_stack[MAX_CODE]; //存放解码端解出字符串的数组,DecodeString函数解出的字符串的编号会放在这
void LZWDecode( BITFILE *bf, FILE *fp){//bitfile是自己定义的结构体,便于进行位操作
int character;
int new_code, last_code;
int phrase_length = 1;
unsigned long file_length,num,num_1;
fseek(bf->fp, 0, SEEK_END);
num_1 = ftell(bf->fp);//读出文件字节数
fseek(bf->fp, 0, SEEK_SET);
file_length = BitsInput( bf, 4*8);//原文件的字节数
num = (num_1-4)/2;//包含的词条数
if( -1 == file_length) file_length = 0;
unsigned short* buffer = new unsigned short[num];//存放词条编号的数组
int i = 0;
InitDictionary();//初始化字典,在词典中写入所有的单字节字符
last_code = -1;
for (int i = 0; i < num; i++)buffer[i]=BitsInput(bf,2*8);//读入所有的词条编号
while (i < num)
{
if (last_code=-1)//当是第一个编号的时候
{
DecodeString(0,buffer[i]);//解码编号
new_code = d_stack[0];//把第一个字符当成现自符
fputc(new_code, fp);//输出第一个字符
last_code = new_code;//把现字符当成前缀
}
else//如果不是第一个字符
{
if (buffer[i] < next_code)//如果当前词条编号在字典里
{
int sibling = 0;
phrase_length = DecodeString(0, buffer[i]);//解出字符串,返回字符长度
for (int j = phrase_length-1; j < phrase_length; j--)
{
sibling=InDictionary(d_stack[j], last_code);//查看由目前前缀和刚解出的词条的第一个字符组成的词条是否在字典里(真绕),不在会返回-1,否则返回索引号
if (sibling == -1)//如果不在
{
AddToDictionary(d_stack[j], last_code);//将这个词条写入词典
for(int n=0;n<phrase_length;n++)fputc(d_stack[phrase_length - n - 1], fp);//把解出的字符串输出
}
else//如果在的话
{
for (int n = 0; n < phrase_length; n++)//输出该词条对应的字符串
{
fputc(dictionary[sibling].suffix, fp);
sibling = dictionary[sibling].parent;
}
}
}
last_code = buffer[i];//当前词条成为前缀
}
else//如果当前词条编号不在词典里
{
AddToDictionary(d_stack[phrase_length-1],last_code);//把目前的前缀和前缀的第一个字母组成的词条写入词典(词典编号不在词典里的唯一情况)
phrase_length=DecodeString(0, buffer[i]);
for (int j = 0; j < phrase_length; j++)fputc(d_stack[phrase_length - j - 1], fp);//同上 输出
}
}
i++;
}
}
这是压缩前的文本
这是压缩后的文件
这是解码后的文件