一.问题描述
输入:一个最多包含n个正整数的文件,每个数都小于n,且数据不重复,其中n = 10000000。
输出:按升序排列输出正数列表。
约束:最多有1MB的内存空间可用,有充足的磁盘存储空间可用。运行时间最多几分钟,运行时间为10秒就不需要进一步优化。
二.程序设计与实现概要
(1)如给定表示文件中整数集合的位图数据结构,则可以分三个阶段来编写程序
第一阶段:将所有的位都置为0,从而将集合初始化为空。
第二阶段:通过读入文件中的每个整数来建立集合,将每个对应的位置都置为1。
第三阶段:检验每一位,如果该为为1,就输出对应的整数,有此产生有序的输出文件。
(2)整个程序的思想就是:
1.每个整数有32位,那么它就可以表示32个数,分别对应每bit位为1.
2.然后把10000000个数分为1+N/BITSPERWORD组(相当于有这么多个桶),每组包含接近32个数。
(3)所申请的int数组如下所示:
(4)下面的C语言的实现和C++的实现代码
#include <stdio.h>
#include <memory.h>
#include <stdlib.h>
#define N 10000000 // 最大数
#define BITSPERWORD 32 <span style="white-space:pre"> </span>// 宽度
#define SHIFT 5
#define MASK 0x1F
// 字节位置 = 数据/32;(采用位运算即右移5位)
// 位位置 = 数据%32;(采用位运算即跟0X1F进行与操作)
// 将逻辑位置为i的二进制位置为1
void set(int arr[], int i)
{
arr[i>>SHIFT] |= (1<<(i&MASK)); // arr[i/32] = arr[i/32] | (1<<(i%32))
}
// 测试逻辑位置为i的二进制位是否为1
int test(int arr[], int i)
{
return arr[i>>SHIFT] & (1<<(i&MASK)); // arr[i/32] = arr[i/32] & (1<<(i%32))
}
// 将逻辑位置的二进制位置为0
void clear(int arr[],int num)
{
memset(arr, 0x00, num*4);
}
int main()
{
int *arr; // 用于存储元素是否存在
// 栈的最大空间一般为2M(与系统有关),因此要堆中分配
arr = (int *)malloc(sizeof(int)*(N/BITSPERWORD+1));
clear(arr,N/BITSPERWORD + 1);
// 先在文本文件in.txt中生成N个数
FILE* in_file = freopen("in.txt", "r", stdin);
FILE* out_file = freopen("out.txt", "w", stdout);
if(in_file != NULL)
{
int d;
while(scanf("%d", &d) != EOF)
set(arr, d);
fclose(in_file);
}
if(out_file != NULL)
{
for(int i = 0; i < N; i++)
if(test(arr, i))
printf("%d\n", i);
}
fclose(out_file);
return 0;
}
三.拓展
给40亿个不重复的unsigned int的整数,没有排过序,然后再给一个数,如果快速判断这个数是否在那40亿个数当中。(腾讯面试题)
用位图法:40亿unsigned int,则用位图表示的话需要大小为40亿个bit=4*109 bit=0.5*109 bytes 因此申请的内存只需要大小约为512MB左右,这样在内存每个bit代表一个unsignedint整数,并将每个bit初始化为0,然后将40亿个unsigned int的整数读入,每个unsigned int的整数对应bit设置为1,读入后,最后看所给定的数对应的bit是否为1,是1存在,否则不存在。