`

C/C++面试之算法系列--如何实现用更少的空间表示英文字母(a ~ z)构成char A[n]字符串

阅读更多

本文转自CSDN:http://blog.csdn.net/zhangfulin_hwatop/article/details/8171520

 

××××××××××××××××××××××××××××××××

“如何实现用更少的空间表示英文字母(a ~ z)构成char A[n]字符串” 
××××××××××××××××××××××××××××××××
 
在嵌入式的通信协议开发过程中由于通信实时性等因素要尽量缩短传输报文的长度,即每一个字节的各位都应该重复利用上;
但是在实际的传输过程中,由于传输报文的某些特性,可以考虑将字符进行编码压缩,再传输,这样就可以有效的利用网络带宽;
在接收端再根据压缩的协议对数据进行解码,即可恢复原始的数据序列。
 
下面“用更少的空间表示这个字符串吗?”本质上就是考的压缩算法,只是问题的问法可能不会一下子让大家想到这个方面而已。当我们抓住了问题的本质就会找到合理的解决方案。
“字节压缩”算法是最容易想到的,但关键在于如何将五位数组织起来的同时保证编解码的时间效率,要编码解码方便。
 
以前在一个485的通信协议中遇到了一个问题,使我在这里产生了一点灵感。
在的通信协议中,因为要连续传输多个字节我们设置了0x55传输起始符,0xAA传输结束符号,但在实际的传输数据过程中数据本身可能为0xAA,这就导致了对数据结束判断的失误,一种方法是利用转义字符来解决的,即连续发送两个0xAA表示此0xAA不为结束符,而是正常有效数据;我们采取了另一种方法,即将待传输的连续8个数据(第一个数据的高位恒为0)的高位取出,构造一个新的数据传输出去,这样就解决了0xAA的问题。
 
利用上面的思想,我想到了将五位分解成4 + 1 的方式,即每个数分解成两部分,两个4组合成1个字节,这样连续连续8个字符即可组合成5个字符进行传输了;8、4的编码效率比顺序每5位一存的效率要高。
 
而“26进制编码”的思想是借鉴了面试中长考的几个函数的思想。
而对于“12356819”类型的十进制的字符串转化为十进制的int数,不就是常考的atio(char *in)函数么?考虑过“011000010100”二进制的字符串么?八进制“23104762”呢?
相信这个时候你应该明白了26进制字符串“adkjflmgdsjkflkmgzxy”的存储转换原理了吧
而其解码过程不就是itoa(int k, char *out)函数么?
 
比较而言,“26进制编码”的思想实现起来更为简单,因为有这么多函数可以借鉴,其转换的时间效率更高。但是这个思想不一定很容易想到。
但“字节压缩”算法的转换效率为5/8,节省3/8,而“26进制编码”最多节省1/3,因此“字节压缩”在大量数据需要压缩的情况下存储效率更好。
 
××××××××××××××××××××××××××××××××
××××××××××××××××××××××××××××××××
 
字符串A是由n个小写英文字母(a ~ z)构成的,定义为char A[n]。你能用更少的空间表示这个字符串吗?请写出实现原理和节省的空间比率. 请写出从char A[n]到你的新的储存格式的转换函数。(请用C/C++编程,不允许上机操作)
 
××××××××××××××××××××××××××××××××
 
一 字节压缩
 
实现原理:因为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++;
             
              // 将每个字符的第五位存放起来,07分别对应此字符的第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,outindex,outchar);
       cout<<outchar<<endl;
       outindex++;
       }
 
}
 
××××××××××××××××××××××××××××××××
××××××××××××××××××××××××××××××××
 
二  26进制编码
    
26个字母就当作26进制,然后每6个字母转换为一个long,32位的 4 个字节..  压缩1/3  
0-26个当作26进制对应的26个数符,6个字符组成的字符串当作一个26进制的数.,转换为10进制整数,刚好范围可以被32long容纳,不过存在一点点浪费.   
比如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)函数么?
有兴趣的朋友可以参考前面的帖子“整数转换成字符串看算法的联想
分享到:
评论

相关推荐

    C/C++面试之算法系列--几个典型的内存拷贝及字符串函数实现

    ### C/C++中的典型内存拷贝及字符串函数实现解析 #### 一、内存拷贝函数:`memcpy` 在C/C++编程中,`memcpy`是一个非常基础且重要的内存拷贝函数,它负责将源地址`src`指向的数据块复制到目标地址`dest`所指向的...

    c/C++面试题大全--96页

    【C/C++面试题大全--96页】这篇文章主要探讨了C/C++编程语言中面试时常见的技术问题,特别是关于字符串处理和内存管理的题目。文章的目的是通过深入解析这些问题来提升面试者的技能水平和对基础知识的理解。 首先,...

    C/C++面试大全 华为面试

    ### C/C++面试大全:华为面试相关知识点解析 #### 一、C/C++基础知识与面试题目 根据给定文件中的信息,“C/C++面试大全 华为面试”这份资料主要涵盖了C/C++语言的基础知识以及华为公司对于这些知识点的具体面试...

    C/C++面试大全--算法和知识点详析

    在C/C++编程领域,面试通常会涵盖多个主题,包括但不限于语言特性、数据结构、算法、设计模式以及操作系统等基础知识。以下是一些基于提供的标题和描述中的知识点的详细分析: 1. **多态性与虚函数表**: - 多态性...

    C/C++面试题

    ### C/C++ 面试题解析 #### 1. N阶乘实现 阶乘是一个经典的递归问题,用于计算正整数`n`的阶乘`n!`。递归实现如下: ```cpp #include using namespace std; int factorial(int n) { if (n ) { cout ; return -1...

    C/C++程序员面试宝典

    《C/C++程序员面试宝典》是一本专为准备C/C++编程面试的求职者精心编写的指南。这本书以PDF格式提供,具有清晰的目录结构,使得读者可以快速定位到感兴趣或需要复习的知识点,有助于高效学习和查阅。在追求理想的...

    C/C++-内存分配算法-操作系统课程设计-首次适应算法-循环首次适应算法-最佳适应算法-最坏适应算法

    用C语言实现动态分区分配过程alloc()和回收过程free()。其中,空闲分区通过空闲分区链表来管理,分别采用首次适应算法、循环首次适应算法、最佳适应算法、最坏适应算法进行内存块的分配和回收,同时显示内存块分配和...

    用C/C++实现的各种经典算法以及常见面试题

    本资源“用C/C++实现的各种经典算法以及常见面试题”正是针对这两方面的学习和提升。 首先,经典算法是计算机科学的基础,包括排序、查找、图论、动态规划等。例如: 1. **排序算法**:快速排序、归并排序、堆排序...

    腾讯c/c++面试题

    从给定的文件信息中,我们可以提炼出一系列与C/C++相关的知识点,这些知识点涵盖了面试题目的设计、数据结构、算法、操作系统基础以及面试策略等多个方面。以下是对这些知识点的详细解析: ### C/C++基础知识 1. *...

    c/c++面试指南

    ### C/C++面试指南知识点概览 #### 第一篇:求职 **1.1 企业与人才** - **1.1.1 企业需要什么样的人才**: - 技术能力:掌握C/C++语言的基本语法及特性。 - 项目经验:具备实际项目开发经验,了解软件开发流程...

    C/C++程序员面试指南.杨国祥(带详细书签).pdf

    本书以流行的面试题讲解为主要内容,介绍了C、C++语言基本概念,包括保留字、字符串、指针和引用、结构体、库函数等各个方面的基础知识,介绍了面向对象编程基本概念,包括如何实现继承、多态和封装等。还介绍了排序...

    C/C++,图算法-Dinic最大流量算法

    C/C++,图算法——Dinic最大流量算法

    c/c++ 数据结构算法实现

    这里提到的"数据结构算法实现"压缩包,显然包含了使用C++语言实现的各种常见数据结构和算法。 首先,让我们了解一下数据结构。常见的数据结构包括: 1. **数组**:是最基本的数据结构,允许我们以固定大小的同类型...

    c/c++ 与java互通 AES加密解密,算法ECB

    最近需要和银行pos做数据通讯,银行端算法为java实现的 AES/ECB/PKCS5PADDING我也改不了, c/c++这边实现ECB算法本来就少,PKCS5PADDING的更是没有,索性自己动手。工作原因c和java都得熟悉,因此把java端和c/c++...

    用C语言编写multipart/form-data实现上传文件

    用C语言实现multipart/form-data文件上传,没有用到curl之类的库。之前做个小的日志上传程序写的。

    算法I-IV (C++实现)---带目录.part5

    算法I-IV (C++实现)---带目录.part5

    C/C++面试题库

    《C/C++面试题库详解》 在编程领域,C/C++语言因其高效、底层控制能力强等特点,一直是软件开发中的重要工具,特别是在系统级编程、游戏开发、高性能计算等领域更是不可或缺。因此,对于求职者来说,掌握C/C++的...

    C/C++手撕代码 算法实现 最短路径 Dijkstra算法与Floyd算法

    C/C++手撕代码 最短路径 Dijkstra算法与Floyd算法-C/C++手撕代码算法实现 最短路径算法实现 Dijkstra算法实现 Floyd算法实现

    SHA-1算法C++实现

    - SHA-1算法使用5个32位的寄存器A、B、C、D和E,初始值分别为:A0 = 67452301, B0 = EFCDAB89, C0 = 98BADCFE, D0 = 10325476, E0 = C3D2E1F0。 2. **消息填充**: - 输入消息首先会被添加一个1比特的'1',然后...

    C/C++,组合算法-K人活动选择问题(Activity-Selection-Problem)的源程序

    C/C++,组合算法——K人活动选择问题(Activity-Selection-Problem)的源程序 1 活动选择问题 Activity-Selection-Problem with k persons 给定两个大小为 N 的数组S[]和E[]表示商店的开始和结束时间,以及一个整数...

Global site tag (gtag.js) - Google Analytics