26个字母压缩编码
一 字节压缩
实现原理:因为a~z的ascii码用不了一个字节表示,减小‘a’后每个字符实际上代表了0-26,五个bit即可表示,压缩后,能省 3/8 的空间, 关键在于如何组织存储顺序便于检索和存储,同时要考虑存储的时间效率
如果将五位按照顺序依次存储,一不好保存,二不好访问
访问按照字节进行,应尽量将 m个数组合成n个字节,能否将8 个字符存成 5 个字节呢?
因此考虑就5位再拆下,分解成最高位和低四位,这样每两个字符就可以转换成一个新的数了;每 8 个字符的最高位可组合成一个字节 ;4、8的规律性很强,组合分解都很方便
××××××××××××××××××××××××××××××××
#define LOWMASK 0x0f
#define HIGHMASK (1<<4)
#define CHARLEN 7
输入参数:inarray[] 待编码的字符数组;inlen数组的长度
输入参数:outarray [] 编码后的字符数组;outlen编码后数组的长度
// unsigned char outarray[] 编码后的字符数组用到了最高位,是有效位,非符号位
void CodeChar(const char inarray[],unsigned int inlen, unsigned char outarray[ ], unsigned int &outlen)
{
int InIndex = 0;
int OutIndex = 0;
int CompoudIndex = 0;
int CompoudValue = 0;
int tempchar = 0;
while(inlen--)
{
tempchar = inarray[InIndex] - 'a'; // 将字符换成数字0-26
if(InIndex%2 == 0) // 偶数位存放在每个字符的高位
outarray[OutIndex] = ( tempchar & LOWMASK) << 4;
else // 奇数位存放在每个字符的低位
outarray[OutIndex] |=(tempchar & LOWMASK);
if((InIndex%2 == 1) && (InIndex != 0)) // 存放完两个数后,增加输出数组的下标
OutIndex++;
// 将每个字符的第五位存放起来, 0 - 7 分别对应此字符的第 7 -第 0 位
CompoudValue |= ((tempchar & HIGHMASK) << 3) >> (InIndex%8);
CompoudIndex++;
if(CompoudIndex == sizeof(char) * 8) // 存放了八个数后,将合成值保存到输出数组
{
CompoudIndex = 0;
outarray[OutIndex] = CompoudValue;
OutIndex++;
}
InIndex++;
}
if(InIndex%2 == 1) // 某个输出字符只用了高四位,但保存合成值的输出下标未增加
OutIndex++;
if(CompoudIndex != 0) // 此时说明输入数组个数非 8 的倍数,最后一个合成值上面未保存
{
outarray[OutIndex] = CompoudValue;
OutIndex++;
}
outlen =OutIndex; // 保存转换完毕后的输出数组长度
}
// 输入参数:inarray 上面编码后的数组;inlen编码后的数组长度;outk待解码字符在原数组中的下标位置
// 输出参数:outchar解码的码值;inlen编码后的数组长度
void DecodeChar(const unsigned char inarray[],unsigned int inlen, unsigned int outk, char &outchar)
{
int CompoudIndex = 0;
int HighValue = 0; // 待解码字符的第五位(转换为0-26之后的)
int LowValue = 0; // 待解码字符的低四位(转换为0-26之后的)
int CompoudValue = 0; // 解码后的值
if(((outk+8)/8 * 5 - 1) < inlen) // 待解码字符的最高位不在最后一个合成值内
HighValue = ((inarray[((outk+8)/8 * 5 - 1 )] >> (CHARLEN - (outk%8))) & 0x01) << 4;
Else // 待解码字符的最高位在最后一个合成值内
{
HighValue = ((inarray[inlen-1 ] >> (CHARLEN - (outk%8))) & 0x01) << 4;
}
if(outk%2 == 0) // 偶数保存在高位
LowValue = inarray[outk/2+outk/8 ] >> 4;
else
LowValue = inarray[outk/2+outk/8] & LOWMASK;
CompoudValue = HighValue | LowValue; // 解码后的数字
CompoudValue += 'a'; // 解码后的字符
outchar = CompoudValue;
}
void main(void)
{
char *in = "abcdefghijklmnuvw";
unsigned char out[100] = {0};
int outindex = 0;
unsigned int outcount;
int incount = strlen(in);
float coderatio = 0;
char outchar;
while(outindex < 100)
out[outindex++] = 0;
CodeChar(in,incount, out, outcount);
out[incount] = '/0'; // 编码
cout<<outcount<<endl;
printf("%s",out);
coderatio = (float )outcount/incount;
printf("%6f",coderatio);
outindex = 0; // 解码
while(outindex < incount)
{
DecodeChar(out,outcount,outchar,outindex);
cout<<outchar<<endl;
outindex++;
}
}
××××××××××××××××××××××××××××××××
××××××××××××××××××××××××××××××××
二 26进制编码
26 个字母就当作 26 进制 ,然后每6个字母转换为一个long,32位的 4 个字节.. 压缩 1/3
0-26个当作26进制对应的26个数符,把 6 个字符组成的字符串当作一个 26 进制的数 .,转换为10进制整数,刚好范围可以被32 位 long 容纳 ,不过存在一点点浪费.
比如a代表1,b代表2 ,ab代表26进制的12, 即十进制的28;
比如a代表1,b代表2 ,ab代表26进制的12, 即十进制的28;
反过来从long X得到原始的字符
X/26^5 即得到六个字符的高位,依次类推;但对于最后一个整型数应该特殊考虑。
就相当于十六进展和十进制的转换问题
××××××××××××××××××××××××××××××××
#define AZMAX 26
//输入参数:inarray[] 待编码的字符数组;inlen数组的长度
//输入参数:outarray [] 编码后的整型数组; outlen编码后数组的长度
// unsigned int outarray[] 编码后的整型数组用到了最高位,是有效位,非符号位
void AzCodeChar(const char inarray[],unsigned int inlen, unsigned int outarray[], unsigned int &outlen)
{
int InIndex = 0;
int OutIndex = 0;
unsigned int CompoudValue = 0;
while(inlen--)
{
CompoudValue = CompoudValue* AZMAX + (inarray[InIndex] - 'a'); // 将字符换成数字0-26
InIndex++;
// 按照字符的顺序依次乘 26 ,即把各个数对应的值的和求出来了
if(InIndex%6 == 0) // 六个字符转化为一个整型数
{
outarray[OutIndex] = CompoudValue;
CompoudValue = 0;
OutIndex++;
}
}
if(InIndex%6 != 0) // 不足六个字符,上面未保存
{
outarray[OutIndex] = CompoudValue;
CompoudValue = 0;
OutIndex++;
}
outlen =OutIndex; // 保存转换完毕后的输出数组长度
}
// 输入参数:inarray 上面编码后的数组;inlen编码后的数组长度;outindex待解码字符在原数组中的下标位置;outlen 待解码字符的原数组总长度(比按 5 位编码多了一个参数)
// 输出参数:outchar解码的码值
void AzDecodeChar(const unsigned int inarray[],unsigned int inlen, unsigned int outindex, unsigned int outlen, char &outchar)
{
int DecodeValue = 0; // 解码后的值
unsigned int DevideIndex = 0;
unsigned int DevideValue = 1;
if(outindex < (inlen - 1)*6) // 待解码字符不在最后一个合成值内
{
DevideIndex = 5 - outindex%6; // 得到待除的数基于26的幂指数
}
else // 待解码字符在最后一个合成值内
{
DevideIndex = 5 - (inlen*6 - outlen) - outindex%6; // 此时最高位的意义变小了
}
while(DevideIndex > 0)
{
DevideValue *= AZMAX; // 得到待除的数26^ DevideIndex
DevideIndex--;
}
DecodeValue = (inarray[outindex/6]/DevideValue)%AZMAX; // 得到当前的个位数
outchar = DecodeValue + 'a';
}
void main(void)
{
char *in = "abcdefghijklmnuvw";
unsigned int out[100] = {0};
int outindex = 0;
unsigned int outcount;
int incount = strlen(in);
float coderatio = 0;
char outchar;
while(outindex < 100)
out[outindex++] = 0;
AzCodeChar(in,incount, out, outcount);
cout<<outcount<<endl;
coderatio = (float)outcount * sizeof(int) /incount;
printf("%6f/n",coderatio);
outindex = 0; // 解码
while(outindex < incount)
{
AzDecodeChar(out, outcount, outindex, incount, outchar);
cout<<outchar<<endl;
outindex++;
}
}
××××××××××××××××××××××××××××××××
××××××××××××××××××××××××××××××××
细心的读者可能会发现,上述将字符串看成26进制的字符串,然后再将其编码为十进制,即int类型存起来;
而对于“12356819 ”类型的十进制的字符串转化为十进制的 int数,不就是常考的atio(char *in) 函数么? 考虑过“011000010100”二进制的字符串么?八进制“ 23104762 ”呢?
相信这个时候你应该明白了26进制字符串“adkjflmgdsjkflkmgzxy”的存储转换原理了吧
而其解码过程不就是 itoa(int k, char *out) 函数 么?
有兴趣的朋友可以参考前面的帖子“从 “ 整数转换成字符串 ” 看算法的联想 ”