一、背景介绍
本文主要是对编程珠玑中第一章的学习总结。问题背景大致如下:一个最多包含n个正整数的文件,每个数都小于n,其中n=10^7,同时所有的正整数不重复出现。现在需要按升序将所有的整数输出,约束条件是最多有1MB的内存空间可用,但磁盘空间充足。
如果按通常的处理的方法,使用int类型(32位)存储整数,这样1MB的空间可存储10^6B/4B,即约250000个整数,虽然不能同时对所有的数据进行排序。但也可以采取多趟遍历磁盘的方法完成排序工作,但却IO开销大耗时多。于是文中便提出了利用位图(位向量)的方法来解决问题。
二、位图实现
1、实现介绍
例如要表示集合{1,2,3,5,8,13},可采用20位的字符串“01110100100001000000”表示。在问题中每个7位的十进制数表示一个小于1000万的整数,于是我们可以使用一个具有1000万个位的字符串来表示这个文件,其中当且仅当整数i在文件中存在时,第i位为1。
2、C语言实现
由于C语言中没有实现位图的数据结构(c++中有bitset集合),需要通过位运算来实现。下面的C语言程序采用了int来实现位图。
假设总的位图位数为N(题设为1000万),则需要的int数组的位数为(N/32 + 1),所以定义了数组int a[N/32 + 1]。
对于逻辑上的第i位,其存储的位置则为第i/32(位运算采用i>>5,即将i右移5位)个int位置,位位置为i%32(位运算采用i&0x1F实现,即i与数31进行“与”运算)。
所以对逻辑上第i位置1(函数set)为:a[i>>5 ] = a[i>>5] | 1<<(i&0x1F)。即将1左移(i&0x1F)位后再和a[i>>5]进行“或”运算。例如当i等于33时,由33/32及33%32知逻辑位33由a[1]的第1位表示(含第0位)。位运算中,[i>>5]即为a[1],1<<(i&0x1F)结果为2(即0000 0010),假如此时a[1]为0(即0000 0000),所以或运算的结果是将a[1]的第1位置1。
c语言实现代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define N 10000000
int a[N/32 + 1];
//将逻辑上第i位置1
void set(int i)
{
a[i>>5 ] = a[i>>5] | 1<<(i&0x1F);
}
//查看逻辑上第i位状态
int test(int i)
{
return a[i>>5] & (1<<(i&0x1F));
}
//将逻辑上第i位置0
void clear(int i)
{
a[i>>5] = a[i>>5] & (~(1<<(1&0x1F)));
}
//清零
void pre()
{
int i;
for(i = 0; i< N; i++)
clear(i);
}
int main()
{
FILE *fp;
srand((unsigned)time(NULL));
int count = 0;
int t, i;
if( (fp = fopen("input.txt", "r")) == NULL )
{
printf("can not open the file!\n");
exit(0);
}
//对位图清零
pre();
//读入文件,将读入数据对应逻辑位置1
while(fscanf(fp, "%d", &t) != EOF){
set(t);
}
//输出排序结果
for(i = 0; i < N; i++)
{
if(test(i)){
printf("%d\n", i);
}
}
fclose(fp);
return 0;
}
三、其他
在编程珠玑第一章的习题中,包括了将内存上限严格限制为1MB(习题5),因为上面的实现实际是需要1.25MB的。其实只要理解了前面的内容也还是很好实现的,最关键的两个思想是位图和多趟排序。以及习题6中将题目条件改为每个整数最多重复10次,则可以用4位来记录每个整数的出现次数(好吧 我开始的想法是用10位~)。
以上。