本方介绍一种简单的数据压缩算法:SB (simple block)
作者:realxie
在对文档进行倒排的时候经常涉及到对倒排文档的压缩,例如假设有单词instance 出现的文档有<1,3 ,4 , 6 ,9 ,10,... >,即倒排表以升充排列,这样我们就可以在倒排文件中只保存前后两个文档位置的差量,即转变之后结果为<1 ,2 , 1 , 2 , 3 , 1 , ...>,因此在遍历的时候可以累积得到每个文档位置。分析转变后的倒排文件我们可知 (不失一般性)保存了更多的小量。如果在内存中我们以一个int类型来保存更多类似于1-10这样的小量就会造成更多的浪费,这是因为1-10可以不超过 4bit的空间来进行存储,而int占用32bit,因此会造成大量浪费。如何压缩数据来节约更多的存储空间有待我们解决。
目前已经有各种各样的算法来解决不量问题:
全局方法:Unary , Binary , r ...
局部方法:Bernoulli , ...其它
纵观各种各样的方法实现起来都是相当的困难,并且具有较高的时间复杂度。
下面我来介绍一种我自己开发,相当简单的一个算法,思想简单,实现简单,运行高效,并且具有相当好的压缩效果,当然不是最后,毕竟简单的背后肯定会 做出一定的牺牲。
算法思想:
首先说明该算法采用不定长编码,设编码的基本块长度为BIT,则每个数字的编码长度为BIT的整数倍。
每个基本块的最后一位为标志位,当最后一位为0时说明编码结束,此时我们可以得到一个整数,相反当最后一位不为0时,则要与后面的基本块一起构成一 个更大的整数。
例如:设BIT = 5;则基本块所能表示的最大值为15,下面给出一些例子
15:-> 11110
7 : -> 01110
现在你可能会问,如果要表示的数大于15怎么办,那我们就使用多个基本块来表示一个数据,此时前面(非最后)基本块的最后一位必须为1,设基本块的 数量为b , 那么最大可以表示的数据为2^(b*(BIT-1))-1,这就是说所有基本块的位除了标志位之外都参与数据的二进制表示。如此:
255:-> 11111 11110
2099: ->10001 00111 00110
解码的时候只需要顺序读入每个基本块就可以确定能否得到一个数据。
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
#define MAX 0xFFFFFF
#define BIT 4
int buff[MAX];//保存差量数据
int temp[MAX];//保存解码后的结果
char code[MAX];//保存压缩后的结果
int cmp(const void * a , const void * b)
{
return *(int*)a - *(int*)b;
}
//编码,压缩
//data,待压缩数据,len侍压缩数据个数
//返回值:压缩后所占位数
int encode(int * data , int len);
//解码
//code ,压缩后的数据 , len:压缩后占用的位数
//data:保存解码后的数据,返回值:解码后数据个数
int decode(char * code , int len , int * data);
//测试数据data需要要多少位可以表示
int bsize(int data);
//将data转换成基本块,b表示data需要b位方可表示出
//return :转换后的数据
int block(int data , int b);
//将一个数据以二进制的形式打印
int print(unsigned int data);
int main()
{
//init
int t = MAX << 2;
memset(buff ,0 , sizeof(buff));
for(int i=0;i<MAX;i++){
int temp = rand()%t;
buff[temp>>2] = temp;
}
qsort(buff ,MAX , sizeof(int) , cmp);
int num(0) , i(0);
while(!buff[i])i++;
buff[num++] = buff[i++];
for(;i<MAX;i++ , num++)buff[num] = buff[i] - buff[i-1];
//init 上部分只是得到差量数据
int sum = encode(buff , num);
//测试平均所需位数
cout<<sum * 1.0 / num<<endl;
int tnum = decode(code , sum , temp);
//测试解码后数据是否正确
for(int i=0;i<num;i++)
if(temp[i] != buff[i]){
cout<<"error:\t"<<i<<"\t"<<buff[i]<<"\t\t"<<temp[i]<<endl;
break;
}
return 0;
}
//计算data需要的字节数
int bsize(int data)
{
int b = 0;
while(data) data >>= 1 , b++;
return b;
}
int block(int data , int b)
{
int t = (1<<(BIT-1))-1;
int res = 0;
for(int i = 0; i<b;i++){
if(i)t <<= (BIT-1);
int tmp = data & t;
tmp <<= (i+1);
res |= tmp;
}
for(int i=b-1;i>0;i--)
res |= (1<<(BIT*i));
res <<= (32 - BIT * b);
return res;
}
int encode(int * data , int len)
{
int sum = 0;
memset(code , 0 , sizeof(code));
for(int i=0;i<len;i++){
int b = bsize(data[i]);
int l = b/(BIT-1);
if( b % (BIT-1)) l++;
int tl = sum >> 3;
int tm = sum & 7;
unsigned int tmp = block(data[i] , l);
if(tm == 0){
while(tmp){
code[tl] = 0;
code[tl] |= tmp >> 24;
tmp <<= 8;
tl++;
}
sum += l * BIT;
} else {
code[tl] |= (tmp >> (24 + tm));
tl++;
tmp <<= (8-tm);
while(tmp){
code[tl] = 0;
code[tl] |= tmp >> 24;
tmp <<= 8;
tl++;
}
sum += l * BIT;
}
}
return sum;
}
int decode(char * code , int sum , int * data)
{
int p = 0 , res = 0 , bsize = 0 , num = 0;
num = 0;
while(p<sum){
int tb = p >> 3;
int tm = p & 7;
int bit = (code[tb]>>(7-tm)) & 1;
res = (res<<1) + bit;
bsize ++;p++;
if(bsize % BIT == 0){
res >>= 1;
if(bit) continue;
else {
data[num++] = res;
bsize = 0;
res = 0;
}
}
}
return num;
}
int print(unsigned int data)
{
if(data == 0)return 0;
print(data/2);
cout<<data%2;
return 0;
}