互联网公司面试很喜欢问海量数据的查找(查找某个数,查找重复的数,查找未出现的数等等)、排序(全部排序、部分排序,找出第k大的数,找出前k大的数等等)等问题,基本的问题都是数据量很大(内存不够存放——降低空间复杂度)和性能要求高(运行时间有要求——减低空间复杂度)
这里列出一些常见的一些问题来处理
已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数。
8位最多99 999 999,大概需要99m个bit,大概10几m字节的内存即可。 (可以理解为从0-99 999 999的数字,每个数字对应一个Bit位,所以只需要99M个Bit==1.2MBytes,这样,就用了小小的1.2M左右的内存表示了所有的8位数的电话)2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。
将bit-map扩展一下,用2bit表示一个数即可,0表示未出现,1表示出现一次,2表示出现2次及以上,在遍历这些数的时候,如果对应位置的值是0,则将其置为1;如果是1,将其置为2;如果是2,则保持不变。或者我们不用2bit来进行表示,我们用两个bit-map即可模拟实现这个2bit-map,都是一样的道理。给你A,B两个文件,各存放50亿条URL,每条URL占用64字节,内存限制是4G,让你找出A,B文件共同的URL。如果是三个乃至n个文件呢?
解决方案:
将数据文件分割为20个文件,然后将每个文件内的url进行hashcode计算,然后存入长度为该hashcode结果值最大值得长度的BitSet中,例如hashcode最大值为99999999,那么bitset的大小就应该是9千多万位,实际上bitset大小最多可容纳2的32次方位,即4294967296,40多亿,如果存在此Hashcode则为1,否则为0,最后将所有位为1的数据取出来就去重了。
此为单bitset想法,而布隆算法其实就是多个bitset,多个hash,防止冲突而已
- 假设要你写一个网络蜘蛛(web crawler)。由于网络间的链接错综复杂,蜘蛛在网络间爬行很可能会形成“环”。为了避免形成“环”,就需要知道蜘蛛已经访问过那些URL。给一个URL,怎样知道蜘蛛是否已经访问过呢?
简单的来说就是大数据量文件的查找或者去重类似这样的场景
c++中bitset的一些操作
bitset b; b有n位,每一位默认为false(0),
bitset b(s,pos,m,zero, one); b是string s从pos开始m个字符的拷贝,string中的字符只能是zero或one
public void set(int pos): 位置pos的字位设置为true。
public void set(int bitIndex, boolean value) 将指定索引处的位设置为指定的值。
public void clear(int pos): 位置pos的字位设置为false。
public void clear() : 将此 BitSet 中的所有位设置为 false。
public int cardinality() 返回此 BitSet 中设置为 true 的位数。
bitset排序(关键,元素不能重复)
我们要对0-7内的五个元素进行排序, 假设这5个元素为(4, 7, 2, 5, 3)。 假设元素没有重复的。 要表示8个数, 我们需要8个bit。也就是1byte.
下面我们只需要开辟1byte的空间, 对这个1byte的所有的bit都初始化为0。
如下: 0000 0000
我们可以规定, 有如下的map。
0000 0000 —> 0
0000 0010 —> 1
0000 0100 —> 2
0000 1000 —> 3
0001 0000 —> 4
0010 0000 —> 5
0100 0000 —> 6
1000 0000 —> 7
以上的map是我们默认的, 不需要去另外开辟空间存这个映射表格(不过我们也可以吧index设为从1开始, 就有如下映射:
0000 0001 —-> 1
0000 0010 —-> 2
………………………..
1000 0000 ——-> 8
)
试着想想, 如果我们直接存放, 需要 8 x sizeof(int) = 32byte。
试着想想, 如果我们直接存放, 需要 8 x sizeof(int) = 32byte。
但是由于我们使用了bitmap, 我们只需要8 x 1 bit = 1byte既可以。 好节省空间啊。 节省32倍。 太牛了。
下面我们就应用这一个byte存储着5个元素, 得到的存储信息如下:
首先, 输入第一个元素4, 对应如下:
0001 000.
第二个元素输入是7, 所以将第8位设置为1, 变成如下:
1001 0000
最终, 如下:
1011 1100代表着所有的元素4, 7, 2, 5, 3。
然后我们遍历一遍bit区域, 将改为编号是1的编号(索引)输出, 就是(2, 3, 4, 5, 7)。
#include <bitset>
#include <iostream>
using namespace std;
int main() {
int arr[5] = {4, 7, 2, 5, 3};
bitset<8> mybit; // default constructor all to 0
for(int i = 0; i < 5; ++i) {
mybit.set(arr[i]); // set bit arr[i], 即置1
}
cout << mybit << endl;
cout << "sorting using bitmap: ";
for(int i = 0; i < 8; ++i) {
if(mybit[i]) {
cout << i << " ";
}
}
}
下面一个非常重要的问题是如何自己实现bitmap
从上面的描述可以看到,如果数的范围是0~31
则 bitset<32> mybit; 每次读入的数据i需要将
mybit.set; //将第i位设为1,i的范围是[0,31]
这样占用的内存大小是N bit即N/8 byte即4个字节
这里我们用int型的数组来表示bitmap,数组的每一个元素都是int型,32bit,所以一共需要的数组长度为N/32+1
对于某个数i = 32*a+b的形式,那么我们应该将bitmap的第a个位置元素的第b位置为1
上面有几个关键操作
如何求出a,a相当于是i除以32的商,可以i/32或者利用位运算i>>5右移5位
如何求出b, b相当于i除以32的余数,可以i%32或者i&0x1F()x1F表示的是16进制中的31)
这里来说明下 一个数除以8的余数相当于 它与0111(7的二进制)作与操作。
如何将第a个元素的b位置置为1,只需要将1左移b位再和a作或操作即可。
#define WORD 32
#define SHIFT 5 ////移动5个位,左移则相当于乘以32,右移相当于除以32取整
#define MASK 0x1F //16进制下的31
#define N 10000000
int bitmap[1 + N / WORD];
/*
* 置位函数——用"|"操作符,i&MASK相当于mod操作
* m mod n 运算,当n = 2的X次幂的时候,m mod n = m&(n-1)
*/
void set(int i) {
bitmap[i >> SHIFT] |= (1 << (i & MASK));
}
/* 清除位操作,用&~操作符 */
void clear(int i) {
bitmap[i >> SHIFT] &= ~(1 << (i & MASK));
}
/* 测试位操作用&操作符 */
int test(int i) {
return bitmap[i >> SHIFT] & (1 << (i & MASK));
}
int main(void) {
FILE *in = fopen("in.txt", "r");
FILE *out = fopen("out.txt", "w");
if (in == NULL || out == NULL) {
exit(-1);
}
int i = 0;
int m;
for (i = 0; i < N; i++) {
clear(i);
}
while (!feof(in)) {
fscanf(in, "%d", &m);
printf("%d/n", m);
set(m);
}
printf("abnother");
for (i = 0; i < N; i++) {
if (test(i)) {
printf("%d/n", i);
fprintf(out, "%d/n", i);
}
}
fclose(in);
fclose(out);
return EXIT_SUCCESS;
}