参考:1、深入解析数据压缩算法添加链接描述
2、超级简单的数据压缩算法—LZW算法添加链接描述
3、无损数据压缩LZW算法——C++实现添加链接描述
LZW算法压缩、解码之所以各自生成压缩表,就是秉承了从头到尾遍历每个字符(整数标识)的过程中,每识别出一个字符(整数标识)就往后查找压缩表中尚未出现过的第一个字符组合并放进压缩表,后面的字符压缩时均会在前面建立的压缩表中找到自身,这里要么先建立单个字符的压缩表项,要么从头到尾遍历最前面的单个字符,对初始空的压缩表中未出现的字符在遍历到的时候建立压缩表项,后面再次出现压缩表中已有的字符(该单个字符已经出现过)则对该字符往后拉伙直至不在压缩表中出现过,将最后拉伙的有出现在压缩表中的字符串压缩表项作为编码。
解码时也可以要么先建立单个字符的压缩表项,要么从头到尾遍历最前面的码值,对初始空的压缩表中未出现的码值根据其单字符ASCII码值得到映射字符后及时建立压缩表项,同时对该码值与其后码值映射的首字符往后拉伙直至不在压缩表中出现过,添加进压缩表。即每识别一个字符或字符集就要往后找一个没在压缩表中出现过的字符或字符集添加进压缩表,因为后面元素的压缩与解码均使用前面按序生成的压缩表项。
编码和解码实际上采用了相同的形成压缩表规则:每编码(解码)一串字符或一个元素就要后延找在当前压缩表中没出现过压缩表项并放到表里面,因为后面字符串编码或后面元素解码都依赖前面生成的压缩表项,解码时一定会在压缩表中找到该项,因为当时压缩时就是按当前压缩表项去编码的。
void Compress(string strIn,int nBegin,vector<int>& vcode){
//输入字符串strIn和一个ASCII码值最大值+1还要大的大数 用于标识非单个字符的"码值"
//输出压缩后的整数数组vcode
vcode.clear();
map<vector<char>, int>Table; //字符-ASCII码值-出现次数
//先建立单个字母元素的压缩表
int i = 33; //ASCII码的33开始为可打印字符 33是感叹号!
while (i <= 127) {
Table[{char(i)}] = i;
i++;
}
int nBeginTemp = nBegin;
i = 0;
while (i < strIn.size()) {
if (Table.find({ strIn[i] }) == Table.end()) {
cout << "输入字符含不可打印字符!不支持显示!" << endl;
}
else {
if (i == (strIn.size() - 1)) { //最后落单的字符就压栈该单个字符
int code = Table[{strIn[i]}];
vcode.push_back(code);
break;
}
else {
vector<char> prefix = { strIn[i] }; //每写一次文件都要找前缀+后缀找到一个新的标记到压缩表
vector<char> mark = {}; //已经在压缩表中的能标记这段字符的字符串
while (Table.find(prefix) != Table.end() && i <= (strIn.size() - 1)) { //每次写文件的同时总要找到一个新的标记
i++;
mark = prefix;
prefix.push_back(strIn[i]); //前缀增加
}
//至此前缀没出现过 标记mark有收录 可以用来写文件 并伴随压栈前缀为新标记
int code = Table[mark];
vcode.push_back(code);
//Table[mark][1]++; //该标记这就出现过一次了 次数其实没用到 只用标记来写文件
Table[prefix] = ++nBegin;
//Table[prefix][1] = 0;
continue;
}
}
}
vcode.push_back(nBeginTemp-1); //终止标记
return;
}
void UnCompress(string& strOut, int nBegin, vector<int> vcode) {
//传入一个ASCII码值最大值+1还要大的大数 用于标识非单个字符的"码值"
//传入压缩后的整数数组vcode 传出解码后的字符串strOut
map<int,vector<char>>Table; //ASCII码值或大数标记-字符集合
map<vector<char>, int>TableR;//字符集合-ASCII码值或大数标记
int nCount = 0;
//先建立单个字母元素的压缩表
int i = 33; //ASCII码的33开始为可打印字符 33是感叹号!
while (i <= 127) {
Table[i] = { (char)i };
TableR[{(char)i}] = i;
i++;
}
int nBeginTemp = nBegin;
i = 0;
while (i < vcode.size()) {
if (vcode[i] == (nBeginTemp - 1)) return; //遇到结束标记就退出 已经解码完了
vector<char> prefix = {Table[vcode[i]]}; //最前面的元素肯定能找到 后面的元素取决于从头到尾建立的压缩表
//所以每翻译一个元素就要添加一个标记(这也是编码时建立压缩表的规则) 咋能找不到呢?找不到的话当时是怎么编码的呢?
for (int j = 0; j < prefix.size(); j++) strOut = strOut + prefix[j]; //肯定能找到
//后面的元素必须在前面有被建立到压缩表,每翻译一个元素就要添加本元素+下个元素的第一个字符作新的压缩表
i++; //vcode[]后缀下标
int j = 0; //后缀char数组下标
while (TableR.find(prefix) != TableR.end() && i<(vcode.size()-1)&&j<Table[vcode[i]].size()) {
//不能是最后一个终止标记 压缩表中找不到的
prefix.push_back(Table[vcode[i]][j]); //单个元素追加
}
nBegin++;
Table[nBegin] = prefix;
TableR[prefix] = nBegin;
}
return;
}
void main(){
std::string Sstr{ "ABRACADABRABRABRA" };
vector<int> vcode;
int nBegin = 129; //129-1=128用作终止标记 压缩表中除单个字符外的组合字符均以129+1=130开始累计
mySol.Compress(Sstr, nBegin, vcode);
std::string SStr;
mySol.UnCompress(SStr, nBegin, vcode);
cout << SStr << endl;
system("pause");
}