空间换时间问题通常都是一个不大的问题里的一个不小的解决方案。首先要理解代码中函数的一个大致的代价,在输入比较固定,计算输出又比较费劲的时候,常常就是空间换时间发挥作用的时候了。通过几个简单的例子您就会了解。
在Base64编码过程中,将源字节按规则取位得到一定数量的0~63的数的数组,按规则需要将这些数一一对应固定转换成特定字母数字来得到输出字符串。如果用if……else……的方法,会非常麻烦,代码看起来也很低端不给力。于是就有这样的“空间”:
static const char *codes64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
转换的时候非常简单潇洒的用 codes64[x] 即可得到转换该得到的输出。
在算法密集型库当中经常会有类似这样高大上的定义,现在你是不是明白他的意义?
static const unsigned char map64[256] = {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255,
255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255 };
输入是值为0~255的单字节值,输出 = map64[单字节值]. 非常简单不是吗~
再介绍一种实用的空间换时间,这是我运用在从电话拨号数字串中识别号码的国家码和区号码的算法当中,仅介绍原理,代码相信您了解原理后自然能做出来。
通常的做法考虑可能会做一个数据库,或者做一个xml、ini配置。不过光就处理识别问题,也就是流式分析识别开头的号码是不是国家码是不是区号码的问题,还可以有更高效的办法。
考虑有一个数据结构,也就是“空间换时间”当中的“空间”,它以10个szUnit字节为一组于是一组的字节数长度是szBlock,对应索引值有第0、第1、……第9这样十个位置。空间以不定数量的组串联而成。当处理输入如7935555555这样的号码时,从第一个组的[7]号位拿值idx,如果idx是1~254之间,代表需要继续分析,转移到idx组即p = (QbUnit*)(mBase + p[7]*szBlock);取[9]的值,类似的,需要继续移到下一个组再取[3]的值,此时会得到unitHit。这样最终得到并返回 793 这样一个分析出来的串。
typedef unsigned char QbUnit ; //最小基本单元的尺度
//typedef unsigned short QbUnit;
#define szUnit sizeof(QbUnit) //基本单元的长度
#define szBlock (szUnit*10) //组长度
#define unitHit ((QbUnit)(-1)) //代表命中的值
可能您看过之后会有诸多疑问,如
- idx是相对值还是绝对值?
- 中国那么多区号250个以内的组就能够用吗,QbUnit是不是要用short两字节?
- 会不会有重码(即793和79区号并存导致无法识别和表示79区号)?
- 国际区号用这套行不行?
首先,idx是绝对值,组的起始位置就是pGs[idx]=从空间起始地址+idx*szBlock的地方,非常简单。我同样也考虑过当空间不够用新组在255之后的情况下用相对值会好一些,但是这种情况下,会存在不预期的情况。所以假如能确定不会超250组,就可以放心的用绝对idx。那么顺推到第2个问题,中国那么多区号,250个组足够了!QbUnit可以放心的用char(1字节单位)。举个例子您分析一下,中国所有区号只有2位的城市(不包括开头0)10=北京|21=上海|22=天津|23=重庆|24=沈阳/抚顺/铁岭|25=南京|27=武汉|28=成都/资阳/眉山|29=西安/咸阳|20=广州,只用到3个组。第0组放首位,[1]、[2]有值,[1]指向第1组,[0]位命中北京。第0组[2]指向第2组,[0]没有[1]上海[2]天津[3]重庆…… 注意,上述命中只是简单的unitHit,并不包含地名信息。因为我们只处理识别问题,如何显示还需要考虑国际化问题。
会不会有重码?不会!为什么那么笃定?因为电信系统也是用类似方法来处理号码的,电信系统为了能在有限功能的电话交换机上高效快速的处理电话拨号,不能做太复杂的编码规则,所以赫夫曼编码就成了编码的基础,它是不可能同时有79和793这样的区号的。当然这些是我的推论,没有查证权威资料。但是我发现国际码也是一样的情况。我写了一个工具按照我的规则将国际码和中国的区号码都做了生成,输出刚才说的数据结构“空间”,在代码中做了不少的断言(assert)。最终结果是并没有违背断言的情况发生。我做好的中国编码输出字节580,即58个组就搞定了。