《编程珠玑》第二章
问题一:给定一个包含40亿个32位整数的文件,整数排列次序随机。请查找一个此文件中不存在的32位整数。
解答:32位整数共有0xFFFFFFFF个。如果用一个bit标示一个整数,一共需要约537MB内存。如果内存足够,就构建一个这样大的位图,就可以很快找到的不存在的整数了。
问题二:问题一中,如果内存限制为100MB,如何实现?
解答:可以采用之前提过的多通道算法,对文件读取六次,每次只读取 [(0xFFFFFFFF / 6) * (n - 1), (0xFFFFFFFF / 6) * n] 范围内的整数。
问题三:问题一中,如果内存限制为只有数百字节,如何实现?
解答:如果采用前面提到的多通道算法,也可以实现,但是这样会需要划分成数百万个通道,并且对文件进行数百万次遍历,代价过大,得不偿失。
换个思路,我们不去对32位整数的数值范围进行划分,转而针对整数的bit位进行划分。这些整数的bit位只有32个,每个bit的取值只有0和1。
首先读取一遍文件,分别记录下bit 0为0和1的整数个数。选择个数较少的一种。 第二遍读取文件,在前一次选择的整数中,分别记录下bit 1为0和1的整数个数。选择个数较少的一种。 第三遍读取文件,在前一次选择的整数中,分别记录下bit 2为0和1的整数个数。选择个数较少的一种。 …… 第三十二遍读取文件,在前一次选择的整数中,分别记录下bit 32为0和1的整数个数。
在上述三十二次连续读取过程中,一定会有遇到某次读取完成后,某bit位的某一类值的个数为0的情况。此时,我们就可以输出遗漏的整数:整数的低bit位,由之前的读取过滤规则决定;整数的高bit位可以随意填写;当前统计的bit位直接填写个数为0的那个类别。这样,最多只需要三十二次遍历文件就可以完成搜寻。这个方法的缺点是,需要多次遍历文件中所有的整数,尽管每次只需要上一次需要关注的整数的1/2。
问题四:问题三中,能否减少整数的读取和判断次数?
解答:将问题三的解答略作修改,给每个整数增加一个前缀。第一次遍历文件的时候,将对应bit位为1的整数加上前缀1写入文件1,将对应bit位为0的整数加上前缀0写入文件2。统计完成,下一次遍历直接选取本次生成的两个文件之一。这样每次遍历的整数数量都不超过上一次遍历的整数个数的一半,可以大大减少整数的读取和判断次数。只是需要借助临时文件。
问题五:给定一个包含43亿个32位整数的文件,整数排列次序随机。请查找一个此文件中至少出现两次的32位整数。
解答:如果采用问题一的方法,在向位图的某个bit写入整数存在标志1的时候,判断该bit位是否已经置1。若是,则该整数重复。
如果采用问题二、问题三、问题四的方法,将“选择个数较少的一种”改为“选择个数较多的一种”,将“查找某一类统计数据为0则退出”改为“最后一次遍历查找某一类统计大于1则返回”即可。