赞
踩
目录
面试题【腾讯】:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中
先考虑内存的问题:40亿个整数, 每个整数4字节,换算大约16G的空间,寻常的查找算法都是不可能完成的。因此引入位图。
所谓位图,就是用每一位比特位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的 ,位图实际上也是使用哈希思想,只不过是哈希的变形
通过这种同样是哈希的思想,就可以极大的节省空间,无符号整形范围在0~2的32次方-1,因此我们可以开2的32次方个比特位,换算下来只有512M的大小,通过直接映射就能够判断。
我们无法开这么大的数组,但我们采用的是bit位的标记,即值是几,就把第几个位标记成1。
那么如何去找呢?实际上我们把数组的元素类型规定为char(int也可以),这样就可以通过如下的方式去找任意一个数:x
x映射的值,在第几个char对象上:x/8
x映射的值,在这个char对象的第几个比特位:x%8
比特位顺序问题:
事实上,比特位的顺序与机器的大小端无关,仍是和地址一样从右到左:
1字节 = 8bit,代表的整数就是0~7,比特位为1则表示该数存在
对于位图的功能,要有插入,删除,检测在不在三个功能,如果这样赋值:
插入就可以这样:_bits[i] |= (1 << j);
删除就可以这样:_bits[i] &= (~(1 << j));
测试这个值在不在就可以这样:return _bits[i] & (1 << j);
此外,一开始同样需要开辟指定大小的空间,因此构造函数中要写出来。
- template<size_t N> // 非类型模板参数:用一个整形作为模板的一个参数,在模板中可以当常量使用
- class bitset
- {
- public:
- bitset()
- {
- _bits.resize(N / 8 + 1, 0);
- }
-
- void set(size_t x)//标记
- {
- size_t i = x / 8;
- size_t j = x % 8;
-
- _bits[i] |= (1 << j);
-
- }
- void reset(size_t x)//清除
- {
- _bits[i] &= (~(1 << j));
- }
-
- bool test(size_t x)//测试这个值在不在
- {
- size_t i = x / 8;
- size_t j = x % 8;
-
- return _bits[i] & (1 << j);
- }
- private:
- vector<char> _bits;
- };
下面是int类型的代码
- class bitset {
- public:
- bitset(size_t N) {
- _bits.resize(N / 32 + 1, 0);
- _num = 0;
- }
-
- void set(size_t x) {
- size_t index = x / 32; // 算出x映射的位在第几个整形
- size_t pos = x % 32; // 算出x在这个整形中第几个位置(bit位)
-
- _bits[index] |= (1 << pos); // 第pos个位置成1
- // 左移、右移这里的左右不是方向
- // 左移是向高位移
- // 右移是向低位移
-
- ++_num;
- }
-
- void reset(size_t x) {
- size_t index = x / 32; // 算出x映射的位在第几个整形
- size_t pos = x % 32; // 算出x在这个整形中第几个位置(bit位)
-
- _bits[index] &= ~(1 << pos); // 第pos个位置成0
-
- --_num;
- }
-
- // 判断x在不在
- bool test(size_t x) {
- size_t index = x / 32; // 算出x映射的位在第几个整形
- size_t pos = x % 32; // 算出x在这个整形中第几个位置(bit位)
-
- return _bits[index] & (1 << pos);
-
- }
-
- private:
- std::vector<int> _bits; // 一个int4个字节,一个字节8个bit位
- size_t _num;
- };
测试
- void test_bitset() {
-
- bitset bs(100);
- bs.set(99);
- bs.set(98);
- bs.set(97);
- bs.set(96);
- for (size_t i = 0; i < 100; ++i) {
- printf("[%d]:%d\n", i, bs.test(i));
- }
- cout << bs.test(96);
-
- //bitset bs1(-1);//无符号最大数直接传-1,会转成最大数量
- //bitset bs2(pow(2,32));
- //bitset bs3(0xffffffff);
- }
库中也有位图结构 bitset ,也可以直接使用库中的位图。
#include <bitset>
一. 给定100亿个整数,设计算法找到只出现一次的整数?
对于此整数有三种状态:
- 出现0次
- 出现1次
- 出现一次以上
因此,我们可以通过两个比特位来标记:00代表出现0次;01代表出现1次,其他的代表出现一次以上。(两个标记位可以表示从1到3),此方式虽然可行,但需要第上述代码做出很大变动。那么我们可以采用另一种方式代表两个比特位,即以开两个位图的方式,每个位图的一个比特位组合成两个比特位进行标记
- template<size_t N>
- class solution {
- public:
- void set(size_t x) {
- if (_bs1.test(x) == false && _bs2.test(x) == false) { // 00
- _bs2.set(x); // 01
- }
- else if (_bs1.test(x) == false && _bs2.test(x) == true) { // 01
- _bs1.set(x);
- _bs2.reset(x); // 10
- }
- }
-
- void PrintOnce()
- {
- for (size_t i = 0; i < N; ++i)
- {
- if (!_bs1.test(i) && _bs2.test(i))
- {
- cout << i << endl;
- }
- }
- cout << endl;
- }
-
- private:
- bitset<N> _bs1;
- bitset<N> _bs2;
-
- };
测试
- void test_solution()
- {
- solution<100> tbs;
- int a[] = { 3, 5, 6, 7, 8, 9 ,33, 55, 67, 3,3,3,5,9,33 };
- for (auto e : a)
- {
- tbs.set(e);
- }
- tbs.PrintOnce();
- }
二. 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
方案1: 将其中一个文件1的整数映射到一个位图中,读取另外一个文件2中的整数,判断在在不在位图,在就是交集。消耗5OOM内存
方案2: 将文件1的整数映射到位图1中,将文件2的整数映射到位图2中,然后将两个位图中的数按位与。与之后为1的位就是交集。消耗内存1G。
三. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
本题找的不超过2次的,也就是要找出现1次和2次的
本题还是用两个位表示一个数,分为出现0次00表示,出现1次的01表示,出现2次的10表示出现3次及3次以上的用11表示
总结一下位图的应用:
位图有如下优点:1.节省空间。2.快。但相对的,位图同样有缺点存在:1. 一般要求范围相对集中,如果范围特别分散,空间消耗就会上升。2. 只能针对整形
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。