当前位置:   article > 正文

C++ 哈希的应用--位图

C++ 哈希的应用--位图

目录

位图的引入

位图概念

位图代码

位图应用


位图的引入


面试题【腾讯】:给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

比特位顺序问题:

事实上,比特位的顺序与机器的大小端无关,仍是和地址一样从右到左:

image-20230303193809130

 1字节 = 8bit,代表的整数就是0~7,比特位为1则表示该数存在

位图代码


对于位图的功能,要有插入,删除,检测在不在三个功能,如果这样赋值:

  • size_t i = x / 8;
  • size_t j = x % 8;

插入就可以这样:_bits[i] |= (1 << j);

删除就可以这样:_bits[i] &= (~(1 << j));

测试这个值在不在就可以这样:return _bits[i] & (1 << j);

此外,一开始同样需要开辟指定大小的空间,因此构造函数中要写出来。

  1. template<size_t N> // 非类型模板参数:用一个整形作为模板的一个参数,在模板中可以当常量使用
  2. class bitset
  3. {
  4. public:
  5. bitset()
  6. {
  7. _bits.resize(N / 8 + 1, 0);
  8. }
  9. void set(size_t x)//标记
  10. {
  11. size_t i = x / 8;
  12. size_t j = x % 8;
  13. _bits[i] |= (1 << j);
  14. }
  15. void reset(size_t x)//清除
  16. {
  17. _bits[i] &= (~(1 << j));
  18. }
  19. bool test(size_t x)//测试这个值在不在
  20. {
  21. size_t i = x / 8;
  22. size_t j = x % 8;
  23. return _bits[i] & (1 << j);
  24. }
  25. private:
  26. vector<char> _bits;
  27. };

下面是int类型的代码

  1. class bitset {
  2. public:
  3. bitset(size_t N) {
  4. _bits.resize(N / 32 + 1, 0);
  5. _num = 0;
  6. }
  7. void set(size_t x) {
  8. size_t index = x / 32; // 算出x映射的位在第几个整形
  9. size_t pos = x % 32; // 算出x在这个整形中第几个位置(bit位)
  10. _bits[index] |= (1 << pos); // 第pos个位置成1
  11. // 左移、右移这里的左右不是方向
  12. // 左移是向高位移
  13. // 右移是向低位移
  14. ++_num;
  15. }
  16. void reset(size_t x) {
  17. size_t index = x / 32; // 算出x映射的位在第几个整形
  18. size_t pos = x % 32; // 算出x在这个整形中第几个位置(bit位)
  19. _bits[index] &= ~(1 << pos); // 第pos个位置成0
  20. --_num;
  21. }
  22. // 判断x在不在
  23. bool test(size_t x) {
  24. size_t index = x / 32; // 算出x映射的位在第几个整形
  25. size_t pos = x % 32; // 算出x在这个整形中第几个位置(bit位)
  26. return _bits[index] & (1 << pos);
  27. }
  28. private:
  29. std::vector<int> _bits; // 一个int4个字节,一个字节8个bit位
  30. size_t _num;
  31. };

测试

  1. void test_bitset() {
  2. bitset bs(100);
  3. bs.set(99);
  4. bs.set(98);
  5. bs.set(97);
  6. bs.set(96);
  7. for (size_t i = 0; i < 100; ++i) {
  8. printf("[%d]:%d\n", i, bs.test(i));
  9. }
  10. cout << bs.test(96);
  11. //bitset bs1(-1);//无符号最大数直接传-1,会转成最大数量
  12. //bitset bs2(pow(2,32));
  13. //bitset bs3(0xffffffff);
  14. }

库中也有位图结构 bitset ,也可以直接使用库中的位图。

#include <bitset>

位图应用

一. 给定100亿个整数,设计算法找到只出现一次的整数?

对于此整数有三种状态:

  1. 出现0次
  2. 出现1次
  3. 出现一次以上

因此,我们可以通过两个比特位来标记:00代表出现0次;01代表出现1次,其他的代表出现一次以上。(两个标记位可以表示从1到3),此方式虽然可行,但需要第上述代码做出很大变动。那么我们可以采用另一种方式代表两个比特位,即以开两个位图的方式,每个位图的一个比特位组合成两个比特位进行标记

  1. template<size_t N>
  2. class solution {
  3. public:
  4. void set(size_t x) {
  5. if (_bs1.test(x) == false && _bs2.test(x) == false) { // 00
  6. _bs2.set(x); // 01
  7. }
  8. else if (_bs1.test(x) == false && _bs2.test(x) == true) { // 01
  9. _bs1.set(x);
  10. _bs2.reset(x); // 10
  11. }
  12. }
  13. void PrintOnce()
  14. {
  15. for (size_t i = 0; i < N; ++i)
  16. {
  17. if (!_bs1.test(i) && _bs2.test(i))
  18. {
  19. cout << i << endl;
  20. }
  21. }
  22. cout << endl;
  23. }
  24. private:
  25. bitset<N> _bs1;
  26. bitset<N> _bs2;
  27. };

测试

  1. void test_solution()
  2. {
  3. solution<100> tbs;
  4. int a[] = { 3, 5, 6, 7, 8, 9 ,33, 55, 67, 3,3,3,5,9,33 };
  5. for (auto e : a)
  6. {
  7. tbs.set(e);
  8. }
  9. tbs.PrintOnce();
  10. }

二. 给两个文件,分别有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. 排序 + 去重
  3. 求两个集合的交集、并集等
  4. 操作系统中磁盘块标记

        位图有如下优点:1.节省空间。2.快。但相对的,位图同样有缺点存在:1. 一般要求范围相对集中,如果范围特别分散,空间消耗就会上升。2. 只能针对整形

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/587647
推荐阅读
相关标签
  

闽ICP备14008679号