海量数据处理一:一个实例

题目:

  给定一个输入文件,包含40亿个非负整数,请设计一种算法,产生一个不在该文件中的整数。假定你有1GB内存来完成这个任务。

一、几个数字

  1、40亿==4*109~~22*230==232,也就是说整数就这么多个

  2、1GB==230B==8*230b~~80亿,也就是说如果用一位表示一个整数,可以表示80亿个整数(虽然没有这么多)

二、需要用到的C++基础知识

  1、byte类型:byte并不是C++数据类型的关键字,如果希望使用byte类型,可以用unsigned char类型(8位)

  2、unsigned char 与 char的区别,char类型的首位代表的正负号,所以取值是-128~127。

  3、求类型的最大值 https://msdn.microsoft.com/en-us/library/296az74e(VS.80).aspx

1 #include<iostream>
2 using namespace std;
3 void main()
4 {
5     cout<<INT_MAX<<endl;
6     cout<<CHAR_MAX<<endl;
7     cout<<UCHAR_MAX<<endl;
8 }
View Code

  4、读文件操作

 1 #include<iostream>
 2 #include<fstream>
 3 using namespace std;
 4 void main()
 5 {
 6     ifstream infile;
 7     infile.open("data.txt");
 8     if(!infile){
 9         cerr<<"error:unable to open input file: "<<infile<<endl;
10         return;
11     }
12     char s[10];
13     while(!infile.eof())
14     {
15         infile.getline(s,'
');
16         cout<<s<<endl;
17     }
18 }
View Code

  5、位操作

    << :左移符号   ——  1<<2 ——> 00000001<<2 ——> 00000100(相应的>>)

    &:与操作,都为1取1,其余为0 01010101 & 00001111==00000101(相应的|和 ^)

    +:注意区分一下'+'和'&','+' 是求和,逢2进1

三、位向量

  向量,vector,在Java里面就是一种大小可改变的数组

  位向量,首先是一个向量(数组),每个元素占用1位的内存空间,存放的是0和1。

四、问题解决

  1、思路:按照最开始的分析,如果用一位代表一个整数,那么1G内存完全可以放下

  (1)创建一二包含40亿个比特的位向量BV(采用的是byte严格来讲不叫位向量吧)

  (2)将BV的元素初始化为0

  (3)扫描文件中的所有数字,将代表当前数字的为置为1

  (4)从头遍历BV,返回第一个值为0的索引

  2、数据类型采用的是byte,byte类型占8位,因此可以代表8个整形数字,给定一个数,如何计算它的位置?

    byte []bv = new byte[num]

    0:在第一个元素的第8位

    7:在第一个元素的第1位

    10:10>7,所以第一个元素没有它的位置了,它在第二个元素的第三位

    ...

    对任一个整数n,它所在的位置为byte[n/8]的第1<<n%8位,大概是这样的赶脚:

    7 6 5 4 3 2 1 0 , 15 14 13 12 11 10 9 8,.....

    那给定一个位置,如上,求它代表的数字,即byte[i][1<<j](就先这样表示了)

    byte[0][1<<4] = i*8+4 = 4

    byte[1][1<<[1<<5]=1*8+5 = 13

  3、代码

 1 #include<iostream>
 2 #include<fstream>
 3 using namespace std;
 4 void main()
 5 {
 6     ifstream infile;
 7     infile.open("data.txt");
 8     if(!infile){
 9         cerr<<"error:unable to open input file: "<<infile<<endl;
10         return;
11     }
12     char s[10];
13     int temp=0;
14     unsigned int nInts = INT_MAX+1;
15     cout<<nInts<<endl;
16     unsigned char *bv = new unsigned char(nInts/8); 
17     while(!infile.eof())
18     {
19         infile.getline(s,'
');
20         //cout<<typeid(s).name();
21         temp=atoi(s);
22         cout<<temp<<endl;
23         bv[temp/8] |= 1<<(temp%8); 
24     }
25     for(int i=0;i<sizeof(bv);i++){
26         for(int j=0;j<8;j++){
27             if(((bv[i]>>j)&1) == 0){
28                 cout<<"result:"<<i*8+j<<endl;
29                 return;
30             }
31         }
32     }
33 }
View Code

  为了写出这段代码真是要了亲命了......

  我用古老的VC6.0运行的会报错误,但是结果还是算出来了,一定是VC的问题,嗯...

  但是我没有用海量的数据去测试

  这道题的思路和解法参考的是《面试金典》上的一道题,只不过我改成了C++版本,

  而且貌似它给的代码有些小bug,第18行的判断,未实验,感觉是....反正我的版本改了。

五、进阶:只能使用10MB内存

  遇到这种问题的时候,虽然我不保证能做出来,但是会这样去想:

  给了数据量和内存,先大概估算一下内存能否放的下:

  (1)可以放下,最简单的就是排序求解了

  (2)放不下,那就分而治之,划分为不同的子文件,分别求解

  但是现在好像可以这样想:

  (2)放不下,但是可以用位向量表示,位向量放的下,就可以用位向量求解

  (3)不能用位向量或者位向量也放不下,那就只能另寻他法了。

  对于这道题,我的第一反应也是划分子文件,再求解,但是看到了另一种差不多的方法...

  思路:

  显然现在这种情况(1)(2)都不行了,只能划分。

  10MB=10*220B~223B————221个整数

  所以划分为每块可以存储221个整数的区块

  区块的个数:232/221=211——2000块

  怎么个意思呢:

  稍微调节一下,区块大小220,块数212

  (1)首先扫描整个文件

    如果属于区间[0,220-1],区块1++;如果属于[220,221-1],区块2++...

  (2)各个区块,看各个区块的值是多少

    如果值<220,此区块一定少元素

  (3)在少数字的区块通过前述位向量的方法计算

  问题:

  因为存在重复数字,万一正好每个区间都不少呢?就只能对每一个区块都进行位向量计算吗?

  

  

  

原文地址:https://www.cnblogs.com/naonaoling/p/5251321.html