LZW算法原理及实现
原理
LZW的想在数据中创建一个短语词典。如果在此后的编码过程中又遇到了相同的字典,则用相应的索引号替代,而不是短语本身。
由于LZW字典产生的规则固定,所以不需要额外传递字典;解码端可以采取逆过程重建出来字典并进行解码。
编码
编码的流程大致如下:
步骤1:将词典初始化为包含所有可能的单字符,当前前缀P初始化为空。
步骤2:当前字符C=字符流中的下一个字符。
步骤3:判断P+C是否在词典中
-
如果“是”,则用C扩展P,即让P=P+C,返回到步骤2。
-
如果“否”,则
输出与当前前缀P相对应的码字W;
将P+C添加到词典中;
令P=C,并返回到步骤2
解码
解码的流程大致如下:
步骤1:在开始译码时词典包含所有可能的前缀根。
步骤2:令CW:=码字流中的第一个码字。
步骤3:输出当前缀-符串CW到码字流。
步骤4:先前码字PW=当前码字CW。
步骤5:当前码字CW=码字流的下一个码字。
步骤6:判断当前缀-符串CW 是否在词典中。
-
如果”是”,则把当前缀-符串CW输出到字符流。
- 当前前缀P=PW。
- 当前字符C=当前前缀字符串的第一个字符。
- 把P+C添加到词典。
-
如果”否”,则当前前缀P=PW。
- 当前字符C=当前字符串CW的第一个字符。
- 输出P+C到字符流,然后把它添加到词典中。
实现
位输入输出工具
因为索引号大于等于255,所以有时候1字节不够的,而是用int型数据又会造成大量字节的浪费,所以要根据索引的最大值合理选择存储所需要的位数,这就要用到按位进行输入输出的工具了。
struct bitWriter
{
char buffer;
int pos, bits;
ofstream &out;
bitWriter( ofstream &out ): out(out) {
buffer = pos = bits = 0;}
void WriteBit( int bit )
{
if( pos == 8 )
{
out.write( &buffer, 1 );
pos = 0; buffer = 0;
}
buffer |= ( bit << pos);
pos ++; bits ++;
}
void Write( int value, int len )
{
while( len -- )
{
WriteBit( value & 1 );
value >>= 1;
}
}
void EndWrite()
{
out.write(&buffer,1);
}
};
struct bitReader
{
char buffer;
int pos, len, bits;
ifstream ∈
bitReader( ifstream &in ): in(in)
{
buffer = 0; pos = 8;
in.seekg(0,ios::end);
len = in.tellg(); bits = len * 8;
in.seekg(0,ios::beg);
}
unsigned int ReadBit()
{
if( pos == 8 )
{
buffer = in.get();
pos = 0;
}
int t = buffer & 1;
buffer >>= 1; pos ++;
return t;
}
unsigned int ReadBits( int n )
{
unsigned int ans = 0;
for( int i = 0; i < n; ++ i )
{
ans |= ( ReadBit() << i );
}
return ans;
}
};
编码器
利用Trie树来存储字典。分析发现编码所用的节点只需要存储字符的编码和子节点地址即可。
为了减少存储空间的同时保持一定的速度,使用unorder_map来存储节点地址。
最终时间复杂度为 O ( n ) O(n) O(n), n n n为文件长度。
struct encoderNode
{
int code;
unordered_map< char, encoderNode* > index;
encoderNode(){
code = -1; index.clear(); }
~encoderNode()
{
for( auto i : index )
if( nullptr != i.second )
delete i.second;
index.clear();
}
};
class LZWEncoder
{
private:
encoderNode* root;
encoderNode* pointer;
int tot, maxValue;
vector< int > coded_file;
public:
LZWEncoder() {
root = new encoderNode; pointer = root; tot = maxValue = 255;}
~LZWEncoder() {
delete root; }
void Init()
{
/*构建最初始的字典。*/
root = new encoderNode; tot = 256;
pointer = root;
for( int i = 0; i < 256; ++ i )
{
auto t = root->index[i] = new encoderNode;
t->code = i;
}
}
vector< int > Encode( unsigned char* buffer, int len )
{
int pos = 0;
char c;
while( pos < len )
{
//读取下一个字符
c = buffer[pos ++];
//如果当前字符串p+下一个字符不在字典中
if( nullptr == pointer->index[c] )
{
//将当前字符串p的编号输出
coded_file.push_back( pointer->code );
//将p+c加入字典
pointer = ( pointer->index[c] = new encoderNode );
pointer->code = tot ++;
pointer = root->index[c];
}
else pointer = pointer->index[c]; //如果在字典中就继续读取下一个字符,直到不再其中
}
if( len == pos ) coded_file.push_back( pointer->code ); //将未输出的部分输出
return coded_file;
}
vector< int > Encode( ifstream &in )
{
//重载,直接从文件流中进行解码
int pos = 0; char c;
in.seekg(0, ios::end); int len = in.tellg(); in.seekg(0, ios::beg);
unsigned char *buffer = new unsigned char[len];
in.read((char*) buffer, len );
auto v = Encode( buffer, len );
delete [] buffer;
return v;
}
vector< int > Encode( bitReader &reader )
{
//重载,从位工具中读取数据并解码
char c; int len = reader.len;
for( int i = 0; i < len; ++ i )
{
//读取下一个字符
c = reader.ReadBits(8);
//如果当前字符串p+下一个字符不在字典中
if( nullptr == pointer->index[c] )
{
//将当前字符串p的编号输出
coded_file.push_back( pointer->code );
//将p+c加入字典
pointer = ( pointer->index[c] = new encoderNode );
pointer->code = tot ++;
pointer = root->index[c];
}
else pointer = pointer->index[c]; //如果在字典中就继续读取下一个字符,直到不再其中
}
coded_file

最低0.47元/天 解锁文章
470

被折叠的 条评论
为什么被折叠?



