LZW编码
算法分析
程序设计要点:
编码过程主要难点在于
- 如何设计符合算法流程图所要求的词典?
- 如何根据所给单词快速找出其在词典中的位置?
对于第一个问题,实质上解决的方法是通过数据结构类型的选择,建立一个以码字为下标、字符串为内容的线性表。这在 M a t l a b Matlab Matlab 中可以通过构造元胞字符串数组实现,在 C C C++ 中可通过 S T L STL STL 的 V e c t o r Vector Vector 向量容器直接实现。然而,线性数据结构虽然实现容易,但我们需要考虑到这样一个问题:实际的操作中需要压缩的句子可能很长,如果我们以线性表简单粗暴地建立词典,是最好的选择吗?答案是否定的。这个原因可以通过对问题二的分析得到答案。通过算法流程图的分析可知,算法流程中需要完成查找单词 W 1 W1 W1 是否在词典中与找到单词 W W W 的码字这两个操作。这两种操作共同的特点是:它们需要遍历整个词典。如果我们使用元胞数组或者 V e c t o r Vector Vector 存储时,最直接的想法是,以期望查找的单词为参考对象,遍历整个词典,逐一核对是否与对应词典内容相等,这种做法的时间复杂度是 O ( n ) O(n) O(n), 也就是说,查找的效率将受制于词典生长的程度,显然,当待压缩讯息很长时,词典的生长会很迅速,查找某个单词的时间开销将会增大。有些算法书上会说线性表查找可以使用二分法,时间复杂度就能降低至 O ( l o g n ) O(logn) O(logn),但遗憾的是, L Z W LZW LZW 编码算法中的词典并不满足严格的字典序,因此无法使用二分查找。实际上,有一种数据结构可以实现 L Z W LZW LZW 词典的 O ( l o g n ) O(logn) O(logn) 查找,那就是 C C C++ S T L STL STL 中的关联容器 S e t Set Set 与 M a p Map Map。 S e t Set Set 与 M a p Map Map 容器的底层实现原理是基于红黑树,它是一种非常高效的平衡检索二叉树,其查找某个节点元素的效率正是 O ( l o g n ) O(logn) O(logn) ,符合我们的需求。此外, S e t Set Set 容器的数学意义与数学中的集合概念等价,即存在自动去重的功能;而 M a p Map Map 容器可以实现任意数据类型之间的映射,比如建立以字符串为下标,整数为存储数据类型的数组,可以很好地满足我们的需求。最后,本题中个人认为, C C C++ 处理较 M a t l a b Matlab Matlab 处理更优, M a t l a b Matlab Matlab 对字符串的处理还是比较弱的,仅有字符数组、二维字符数组、元胞数组以及一些基础的 < c s t r i n g > <cstring> <cstring> 字符串处理函数,缺乏高效的字符串算法及数据结构支持。
测试数据
Test case 1
输入数据
A B C A B D A B C A A A A B B B A B C A B C A ABCABDABCAAAABBBABCABCA ABCABDABCAAAABBBABCABCA
输出数据
0 x 41 0 x 42 0 x 43 0 x 100 0 x 44 0 x 100 0 x 102 0 x 41 0 x 107 0 x 42 0 x 109 0 x 105 0 x 10 b 0x41\ 0x42\ 0x43\ 0x100\ 0x44\ 0x100\ 0x102\ 0x41\ 0x107\ 0x42\ 0x109\ 0x105\ 0x10b 0x41 0x42 0x43 0x100 0x44 0x100 0x102 0x41 0x107 0x42 0x109 0x105 0x10b
Test case 2
输入数据
101001101 101001101 101001101
输出数据
2 1 3 4 3 2 2\ 1\ 3\ 4\ 3\ 2 2 1 3 4 3 2
C++ Code
/**
******************************************************************************
* @file Project/Template/LZW_Encode.cpp
* @author 2019051312 LZH
* @version V1.0.0
* @date 29-March-2022
* @brief Main program body
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include<bits/stdc++.h>
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define ll long long
#define ld long double
#define pb push_back
using namespace std;
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
string s,s_current; //s_next:memorized word.
set<string> Dick; //Dictionary
map<string,int> addr; //Address of a word
int p=0; //String pointer
int Dick_size; //Size of dictionary.
/* Private function prototypes -----------------------------------------------*/
/* Private functions -----------------------------------------------*/
/**
* @brief Test case 1 Initialization.
* @param None
* @param None
* @retval None
*/
void Init1(){
cin>>Dick_size;
cin>>s;
Dick.clear();
s_current.clear();
for (int i=0; i<256; i++){
string tmp;
tmp.clear();
tmp+=(char)i;
Dick.insert(tmp);
addr[tmp]=i;
}
s_current+=s[0];
p++;
}
/**
* @brief Test case 2 Initialization.
* @param None
* @param None
* @retval None
*/
void Init2(){
cin>>Dick_size;
cin>>s;
Dick.clear();
s_current.clear();
for (int i=0; i<2; i++){
string tmp;
tmp.clear();
tmp+='0'+i;
Dick.insert(tmp);
addr[tmp]=i+1;
}
s_current+=s[0];
p++;
}
/**
* @brief Main program.
* @param None
* @retval None
*/
int main(){
ios::sync_with_stdio(0);
Init1(); //for test case 1.
//Init2(); //for test case 2.
while (p<s.length()){
set<string>::iterator it;
string s_new;
s_new=s_current+s[p]; //Generate the new word.
it=Dick.find(s_new); //Find whether or not the new word is in the dictionary.
if (it==Dick.end()){ //if new word not exists in the dictionary or overflow.
cout<<"0x"<<hex<<addr[s_current]<<endl; //Print the code of current word.
//add a new word to the dictionary if not overloaded.
if (Dick.size()<Dick_size){
Dick.insert(s_new);
addr[s_new]=Dick.size()-1;
//addr[s_new]++; //for test case 2.
}
//Update current word via current read char
s_current.clear();
s_current+=s[p];
}
else{
//Update current word via new word.
s_current=s_new;
}
p++; //Iterate to next char
}
cout<<"0x"<<hex<<addr[s_current]<<endl;
return 0;
}
/**
* @}
*/
/************************END OF FILE****************/
Matlab Code
s='ABCABDABCAAAABBBABCABCA'; %s='101001101';
s_len=23; %s_len=9;
s_new='';
base=255; %base=1;
p=1;
pp=1;
Dick=cell(500);
for ii=0:base
tmp=char(ii); %tmp=char(48+ii);
Dick{pp}=tmp;
pp=pp+1;
end
s_current=s(p);
p=p+1;
while (p<=s_len)
s_new=strcat(s_current,s(p));
tmp_p=-1;
for ii=1:pp-1
if (strcmp(s_new,Dick(ii)))
tmp_p=ii;
end
end
if (tmp_p==-1)
for ii=1:pp-1
if (strcmp(s_current,Dick(ii)))
tmp_p=ii-1;
%tmp_p=ii;
end
end
fprintf('%x ',tmp_p);
Dick{pp}=s_new;
pp=pp+1;
s_current=s(p);
else
s_current=s_new;
end
p=p+1;
end
tmp_p=-1;
for ii=1:pp-1
if (strcmp(s_current,Dick(ii)))
tmp_p=ii-1;
%tmp_p=ii;
end
end
fprintf('%x\n',tmp_p);
结果展示
Test case 1
C++
Matlab
Test case 2
C++
Matlab
LZW解码
算法分析
程序设计要点:
解码过程的重点主要在于
- 如何快速找出词典中是否存在读入码字所对应的单词?
虽然解码过程中也有“遍历词典、查找单词”的操作,但这个过程是与上述编码过程中的操作逻辑不同的。编码过程中查找单词的逻辑是:已知一个新的未知的字符串,要查找整个词典内是否有已知字符串单词与其匹配,所以无论使用怎样的数据结构维护词典或是使用怎样的查找算法,最坏情况都是需要遍历整个词典。然而,在解码过程中,我们输入的是单词对应的码字,同时我们已知码字的作用在词典中是表示单词元素的下标,因此很显然我们可以通过直接比较码字大小与词典空间大小进行比较快速判断单词在词典中的存在性,即若码字 x x x 小于等于词典 S i z e Size Size, 则存在,否则不存在。这种查找算法的时间复杂度是 O ( 1 ) O(1) O(1),十分完美的常数复杂度。解码过程与编码过程查找单词的差异主要在于:在一个无序词典中,单词元素是不具有单调性的,而码字(数组下标元素)是具有单调递增性的,因而可以直接比较 x x x 与 S i z e Size Size 。
测试数据
Test case 1
输入数据
0 x 41 0 x 42 0 x 43 0 x 100 0 x 44 0 x 100 0 x 102 0 x 41 0 x 107 0 x 42 0 x 109 0 x 105 0 x 10 b 0 x f f f 0x41\ 0x42\ 0x43\ 0x100\ 0x44\ 0x100\ 0x102\ 0x41\ 0x107\ 0x42\ 0x109\ 0x105\ 0x10b\ 0xfff 0x41 0x42 0x43 0x100 0x44 0x100 0x102 0x41 0x107 0x42 0x109 0x105 0x10b 0xfff
输出数据
A B C A B D A B C A A A A B B B A B C A B C A ABCABDABCAAAABBBABCABCA ABCABDABCAAAABBBABCABCA
Test case 2
输入数据
2 1 4 5 2 7 8 0 x f f f 2\ 1\ 4\ 5\ 2\ 7\ 8\ 0xfff 2 1 4 5 2 7 8 0xfff
输出数据
1000000111111 1000000111111 1000000111111
C++ Code
/**
******************************************************************************
* @file Project/Template/LZW_Decode.cpp
* @author 2019051312 LZH
* @version V1.0.0
* @date 29-March-2022
* @brief Main program body
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include<bits/stdc++.h>
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define ll long long
#define ld long double
#define pb push_back
using namespace std;
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
int x; //code
string s; //s:answer
vector<string> Dick; //Dictionary
int code_current; //memorized the current code.
/* Private function prototypes -----------------------------------------------*/
/* Private functions -----------------------------------------------*/
/**
* @brief Test case 1 Initialization.
* @param None
* @param None
* @retval None
*/
void Init1(){
cin>>hex>>x; //input the first code.
s.clear();
Dick.clear();
for (int i=0; i<256; i++){
string tmp;
tmp.clear();
tmp+=(char)i;
Dick.pb(tmp);
}
code_current=x;
s+=Dick[x];
}
/**
* @brief Test case 2 Initialization.
* @param None
* @param None
* @retval None
*/
void Init2(){
cin>>hex>>x; //input the first code.
s.clear();
Dick.clear();
Dick.pb("114514");
for (int i=0; i<2; i++){
string tmp;
tmp.clear();
tmp+='0'+i;
Dick.pb(tmp);
}
code_current=x;
s+=Dick[x];
}
/**
* @brief Main program.
* @param None
* @retval None
*/
int main(){
ios::sync_with_stdio(0);
Init1(); //for test case 1.
//Init2(); //for test case 2.
string s1,s_new;
while (1){
cin>>hex>>x;
if (x==0xfff) break;
if (x<Dick.size()){ // if current word exists.
s_new=Dick[x];
s+=s_new; //Print the word first.
//Update the dictionary.
s_new=Dick[code_current]+s_new[0];
Dick.pb(s_new);
code_current=x; //memorize the code of the new-input word.
}
else{
//Update the dictionary first.
s_new=Dick[code_current]+Dick[code_current][0]; //append the first char of book[code_current] to the end of it.
Dick.pb(s_new);
s+=s_new; //add s_new to the answer.
code_current=Dick.size()-1; //memorize the code of the new-constructed word.
}
}
cout<<s<<endl; //Print the decoding answer.
return 0;
}
/**
* @}
*/
/************************END OF FILE****************/
Matlab Code
%for test case 1.
%x=[0x041,0x042,0x043,0x100,0x044,0x100,0x102,0x041,0x107,0x042,0x109,0x105,0x10b,0xfff];
%for test case 2.
x=[0x002,0x001,0x004,0x005,0x002,0x007,0x008,0xfff];
s='';
s_new='';
code_current=0;
base=1; %for test case 2.
%base=255; % for test case 1.
p=1;
pp=0;
Dick=cell(500);
for ii=0:base
pp=pp+1;
%tmp=char(ii); % for test case 1.
tmp=char(48+ii); %for test case 2.
Dick{pp}=tmp;
end
code_current=x(p);
%code_current=code_current+1; %for test case 1.
s=char(Dick(code_current));
while (1)
p=p+1;
if (x(p)==0xfff)
break;
end
if (x(p)<=pp) % for test case 1, x(p)<pp.
% x(p)=x(p)+1; %for test case 1.
s_new=char(Dick(x(p)));
s=strcat(s,s_new);
s_new=strcat(char(Dick(code_current)),s_new(1));
pp=pp+1;
Dick{pp}=s_new;
code_current=x(p);
else
tmp=char(Dick(code_current));
s_new=strcat(char(Dick(code_current)),tmp(1));
pp=pp+1;
Dick{pp}=s_new;
s=strcat(s,s_new);
code_current=pp;
end
end
disp(s);