LZW编码

一、概述

         LZW的编码思想是不断地从字符流中提取新的字符串,通俗地理解为新 “ 词条 ” ,然后用 “ 代号 ” 也就是码字表示这个 “ 词条 ” 。这样一来,对字符流的编码就变成了用码字去替 换字符流,生成码字流,从而达到压缩数据的目的。
        LZW编码是围绕称为词典的转换表来完成的, LZW 编码器通过管理这个词典完成输入与输出之间的转换。
        LZW编码器的输 入是字符流,字符流可以是用 8 位 ASCII 字符组成的字符串,而输出是用 n 位 ( 例如 12 位 ) 表示的码字流。

二、编码

步骤1:将词典初始化为包含所有可能的单字符,当前前缀P初始化为空。

步骤2:当前字符C=字符流中的下一个字符。

步骤3:判断P+C是否在词典中

(1)如果“是”,则用C扩展P,即让P=P+C,返回到步骤2;

(2)如果“否”,则输出与当前前缀P相对应的码字W;将P+C添加到词典中;令P=C,并返回到步骤2。

LZW编码算法首先初始化词典,然后顺序从待压缩文件中读入字符并按照上述算法执行编码,最后将编得的码字流输出至文件中。

1.词典树的构建及初始化

#define MAX_CODE 256*256     //规定词典数目上限

//构造词典树
struct {   
	int suffix;    //当前字符的尾缀
	int parent, firstchild, nextsibling;  //当前节点对应的母节点、第一个孩子节点、下一个兄弟节点
} dictionary[MAX_CODE + 1];
int next_code;
int d_stack[MAX_CODE]; //存储解码后的信息

//初始化词典
void InitDictionary(void) {
	for (int 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;   //下一个词条的编码
}

词典中默认包含ascii码对应的256个字符,如果想要写入新的词条,则需要从第256个位置开始

2.判断字符是否在词典中

int InDictionary(int character, int string_code) {   //判断词典中是否有当前字符character(尾缀),string_code是旧词条(前缀)
	int s;    //表示字符在词典中的位置
	//string_code=-1,说明是单个字符,已经存在词典中了,直接返回当前字符即可
	if (string_code < 0)  return character;  
	//否则从string_code的第一个孩子节点开始找,如果尾缀相同,返回这个尾缀,始终未找到,则返回-1
	s = dictionary[string_code].firstchild;   
	while (s > -1) {
		if (character == dictionary[string_code].suffix)
			return s;
		s = dictionary[string_code].nextsibling;
	}
	return -1;
}

3.在词典树中添加新字符

void AddToDictionary(int character, int string_code) {  //读入的新字符character(尾缀)和旧词条string_code(前缀)
	int s1, s2;
	//如果string_code=-1,说明是单个字符,直接返回即可
	if (string_code < 0)  return;
	//初始化下一个词条信息
	dictionary[next_code].suffix = character;
	dictionary[next_code].parent = string_code;
	dictionary[next_code].firstchild = -1;
	dictionary[next_code].nextsibling = -1;
	//找到链接的前缀
	s1 = dictionary[string_code].firstchild;
	if (s1 < 0) {
		dictionary[string_code].firstchild = next_code;
	}
	else {
		s2 = s1;
		while (dictionary[s2].nextsibling>-1) {
			s2 = dictionary[s2].nextsibling;
		}
		dictionary[string_code].firstchild = next_code;
	}
	next_code++;
}

4.编码Encode

void LZWEncode(FILE* fp, BITFILE* bf) {
	int character;    //新字符
	int string_code;    //已编码字符,旧词条
	int index;      //索引
	unsigned long file_length;    //文件长度

	fseek(fp, 0, SEEK_END);   //文件指针置文件尾
	file_length = ftell(fp);  //获取文件长度
	fseek(fp, 0, SEEK_SET);   //文件指针置文件头
	BitsOutput(bf, file_length, 4 * 8);   //调用BitsOutput函数
	InitDictionary();    //初始化词典
	string_code = -1;    //初始值赋值为-1,方便在第一次判断的时候判断读取是否为单个字符
	while (EOF != (character = fgetc(fp))) {  
		 //fgetc是从文件中读取一个字符,EOF是文件结束的标志,从文件中读取字符,直到读到结束标志
		 //fgetc是从文件指针stream指向的文件中读取一个字符,读取一个字节后,光标位置后移一个字节。
		index = InDictionary(character, string_code);    //判断当前字符是否在词典中,返回字符在词典中的index,如果不在则返回-1
		if (0 <= index) {	//P+C已经在词典中了
			string_code = index;  //P<-P+C
		}
		else {	
			output(bf, string_code);   //重定义的输出,向编码后的文件中输出旧词条
			if (MAX_CODE > next_code) {	// 如果词典还有空间
				AddToDictionary(character, string_code);    //将P+C添加到词典中
			}
			string_code = character;   //当前字符变为了旧字符,P<-C
		}
	}
	output(bf, string_code);  //循环读完文件后输出最后一个旧字符
}

三、解码

步骤1:在开始译码时词典包含所有可能的前缀根。

步骤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&

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值