LZW 编解码原理
背景
LZW 编码作为词典编码的一种,由 J. Ziv 和 A. Lempel 在 1978 年首次提出并由 Terry A. Welch 在 1984 年改进,最终以三人名字缩写命名。
基本原理
企图从输入的数据中创建一个“短语词典”(Dictionary Of the Phrases),这种短语可以是任意字符的组合。编码数据过程中当遇到已经在词典中出现的“短语”时,编码器就会输出这个词典中的短语的“索引号”而不是短语本身,进而达到压缩的目的。
编码
编码思路
将当前读取到的单字符记为 C(Current),将之前读取的但还未编码的字符或字符串记为 P(Previous),则编码思路可以通过以下流程图呈现
在最终的代码实现中,通过树的思想对词典进行构建,将每一个词条作为树中的一个节点,将每个节点(词条)定义为结构体,具有以下结构
- 尾缀字符(suffix)
- 母节点(parent)
- 第一个子节点(firstChild)
- 下一个兄弟节点(nextSibling)
此时每一个节点就可以用旧节点词条+新字符表示新的节点词条,进而简化查询步骤。
编码举例
现在以字符流“abbababac”举例,分别进行编码步骤演示与树的建立,初始化词典是用 ASCII 进行索引(a = 97,b = 98,c = 99)
步骤数 | P | C | PC 是否在词典中 | 是否输出 P的索引 | 新增词条 | 说明 |
---|---|---|---|---|---|---|
1 | NULL | a | - | - | - | 初始化词典 |
2 | a | b | 否 | 是,输出97 | 256(ab) | ab 不在词典中,扩充词典,然后 P 变为 b |
3 | b | b | 否 | 是,输出98 | 257(bb) | bb 不在词典中,扩充词典,然后 P 变为 b |
4 | b | a | 否 | 是,输出98 | 258(ba) | ba 不在词典中,扩充词典,然后 P 变为 a |
5 | a | b | 是 | 否 | - | ab 在词典中,故不需要新建词条,P 变为 ab |
6 | ab | a | 否 | 是,输出256 | 259(aba) | aba 不在词典中,扩充词典,然后 P 变为 a |
7 | a | b | 是 | 否 | - | ab 在词典中,故不需要新建词条,P 变为 ab |
8 | ab | a | 是 | 否 | - | aba 在词典中,故不需要新建词条,P 变为 aba |
9 | aba | c | 否 | 是,输出259 | 260(abac) | abac 不在词典中,扩充词典,然后 P 变为 c |
10 | c | NULL | - | 是,输出99 | - | 无新的字符,结束编码,输出未编码字符 |
相应的树为下图所示
以词条 “bb”为例,他的 suffix 为 b,parent 为 b,firstChild 为 NULL,nextSibling 为 ba
解码
解码思路
将当前码字记为 cW,上一个码码字记为 pW,解码思路可以通过以下框图体现
因为在实时编解码时,解码要比编码晚一个码字,因而存在码字词条刚被创造就被使用但在解码端还没有相应词条的情况(比如上述编码示例中的 “ababa” 字段),所以在查词典时判断若 cW 不在词典时便手动定义当前码字代表内容为“上一码字代表内容+上一码字代表内容的首字符”,以解决此类问题。
解码举例
词典在最开始已被初始化为 ASCII 码表内容,此处以“97 98 98 256 259 99”作为带解码流
步骤数 | pW | cW | cW 是否在词典中 | 输出内容 | 新增词条 | 说明 |
---|---|---|---|---|---|---|
1 | NULL | 97 | 是 | a | - | 97 已经在词典中,因而输出 cW,新增 pW + cW 首字符 |
2 | 97 | 98 | 是 | b | ab: 256 | 98 已经在词典中,因而输出 cW,新增 pW + cW 首字符 |
3 | 98 | 98 | 是 | b | b b: 257 | 98 已经在词典中,因而输出 cW,新增 pW + cW 首字符 |
4 | 98 | 256 | 是 | ab | ba: 258 | 256 已经在词典中,因而输出 cW,新增 pW + cW 首字符 |
5 | 256 | 259 | 否 | aba | aba: 259 | 259 不在词典中,因而输出pW + pW 首字符,并将其添加到词典中 |
6 | 259 | 99 | 是 | c | abac: abac | 99 已经在词典中,因而输出 cW,新增 pW + cW 首字符 |
代码实现
bitIO.h
#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__
bitIO.c
#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 = fopen( filename, "wb");
if( NULL == bf->fp) return NULL;
bf->mask = 0x80</