当前位置: 首页 > news >正文

【数据结构】哈希应用-STL-位图

目录

1、位图的概念

2、位图的设计与实现

 2.1 set

2.2 reset

2.3 test

3、C++库中的位图

4、位图的优缺点

5、位图相关题目

1、位图的概念

面试题:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。

法一:遍历,时间复杂度是O(N),太慢

法二:排序 + 二分查找。时间复杂度是O(N * logN) + O(logN)。只是第一次比较慢,后面就快了。使用这个方法有一个致命的缺陷是存放40亿个数据需要的内存太过庞大。

1GB = 1024MB = 1024 * 1024KB = 1024 * 1024 * 1024Byte

所以40亿个数据约等于16GB,说明40亿个数据是无法直接放到内存中的,只能放到硬盘文件中。而二分查找只能对内存数组中的有序数据就行查找。这里使用数组是最节省空间的,因为每个位置只存放数据,如果使用红黑树或哈希表需要的空间还要更大

法三:使用位图

数据是否在给定的整型数据中,结果是在或不在,刚好是两种状态,那么可以用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,如果二进制比特位为0,代表不存在。那么我们就可以设计一个用位表示数据是否存在的数据结构,这个数据结构就是位图。

2、位图的设计与实现

实现中要注意的是,C/C++中没有对应位的类型,只能看char/int这样的整型类型,我们再通过位运算去控制对应的比特位。比如我们数据存到vector<int>中,相当于每个Int映射对应的32个值,比如第一个整型映射0~31对应的位,第二个整型映射32~63对应的位,后面依次类推。那么来一个无符号整型x,i = x / 32,j = x % 32,x映射的位置就是vector第i个整型数据的第j位。

我的机器是小端存储,所以一个整型中,低位是在右边

 对于上面40亿个无符号整型,我们开空间需要开2^32个,因为无符号整型有2^32个,不是根据数据个数来开空间 

namespace cxf
{template<size_t N> // 开N个位的位图class bitset{public:bitset(){_bs.resize(N / 32 + 1); // 因为N除32可能会除不尽,所以多开一个整型,保证位够}private:std::vector<int> _bs;};
}

 2.1 set

向位图中插入数据,也就是将插入数据映射到的位标记成1

假设要向位图中插入数据77,要如何操作呢?

首先计算出位为77的地方位于第几个整型数据的第几个位。会发现位于第3个整型数据的第13个位,然后将1左移13个位的结果与第3个整型数据按位或,就可以将插入数据映射到的位标记成1

void set(size_t x)
{size_t i = x / 32;size_t j = x % 32;_bs[i] |= (1 << j);
}

2.2 reset

向位图中删除数据,也就是将传入数据映射到的位标记成0

假设要向位图中删除数据77,要如何操作呢?

首先计算出位为77的地方位于第几个整型数据的第几个位。会发现位于第3个整型数据的第13个位,然后将1左移13个位再按位取反的结果与第3个整型数据按位与,就可以将插入数据映射到的位标记成0

void reset(size_t x)
{size_t i = x / 32;size_t j = x % 32;_bs[i] &= (~(1 << j));
}

2.3 test

若传入数据映射到的位是1就返回真,是0就返回假

bool test(size_t x)
{size_t i = x / 32;size_t j = x % 32;return _bs[i] & (1 << j);
}

可以测试一下

void test_bitset()
{cxf::bitset<100> bs; // 开一个100个位的位图bs.set(77);bs.set(66);cout << bs.test(77) << endl;cout << bs.test(66) << endl;bs.reset(66);cout << bs.test(77) << endl;cout << bs.test(66) << endl;
}

结果是1 1 1 0,是正确的

那要如何开2^32个空间呢?有3种方法

cxf::bitset<-1> bs1;
cxf::bitset<0xffffffff> bs2;
cxf::bitset<UINT_MAX> bs3;
namespace cxf
{template<size_t N> // 开N个位的位图class bitset{public:bitset(){_bs.resize(N / 32 + 1); // 因为N除32可能会除不尽,所以多开一个整型,保证位够}void set(size_t x){size_t i = x / 32;size_t j = x % 32;_bs[i] |= (1 << j);}void reset(size_t x){size_t i = x / 32;size_t j = x % 32;_bs[i] &= (~(1 << j));}bool test(size_t x){size_t i = x / 32;size_t j = x % 32;return _bs[i] & (1 << j);}private:std::vector<int> _bs;};
}

3、C++库中的位图

与前面我们自己实现的位图是差不多的,operator[]可以像数组一样控制某个位

要注意,库中的位图是不能直接开2^32个空间的

void test_bitset2()
{std::bitset<UINT_MAX> bs;
}

像这样程序会崩溃的,因为我们自己实现的位图底层是使用vector,是去堆上开空间,而库中的位图是用一个静态数组实现的,没办法开太大。我们可以对其就行测试

void test_bitset2()
{cxf::bitset<100> bs1;cxf::bitset<10000> bs2;std::bitset<100> bs3;std::bitset<10000> bs4;cout << sizeof(bs1) << " ";cout << sizeof(bs2) << " ";cout << sizeof(bs3) << " ";cout << sizeof(bs4) << " ";
}

结果是16 16 16 1256

当然,是可以通过指针来解决的

std::bitset<-1>* ptr = new std::bitset<-1>();

4、位图的优缺点

优点:增删查改快,时间复杂度均为O(1),节省空间

缺点:只适用于整型

5、位图相关题目

位图的应用:

题目一:给定100亿个整数,设计算法找到只出现一次的整数。

注意:此时虽然是100亿个整数,但是还是按范围开空间,所以还是开2^32个位,与前面一样

法一:可以用两个位来标记一个数,00表示没出现过,01表示出现了1次,10表示出现了2次及以上法二:用两个位图,一个数在每个位图中各占一个位,规则与法一相同

题目二:一个文件有100亿个整数,1G内存,设计算法找到出现次数不超过2次的所有整数

与上面类似,只不过这里是00表示没出现过,01表示出现了1次,10表示出现了2次,11表示出现3次及以上

我们来复用前面实现的位图来对这两个问题就行实现

namespace cxf
{template<size_t N> // 开N个位的位图class bitset{public:bitset(){_bs.resize(N / 32 + 1); // 因为N除32可能会除不尽,所以多开一个整型,保证位够}void set(size_t x){size_t i = x / 32;size_t j = x % 32;_bs[i] |= (1 << j);}void reset(size_t x){size_t i = x / 32;size_t j = x % 32;_bs[i] &= (~(1 << j));}bool test(size_t x){size_t i = x / 32;size_t j = x % 32;return _bs[i] & (1 << j);}private:std::vector<int> _bs;};template<size_t N>class twobitset{public:void set(size_t x){bool bit1 = _bs1.test(x);bool bit2 = _bs2.test(x);if (!bit1 && !bit2) // 00->01{_bs2.set(x);}else if (!bit1 && bit2) // 01->10{_bs1.set(x);_bs2.reset(x);}else if (bit1 && !bit2) // 10->11{_bs2.ser(x);}}int get_count(size_t x) // 返回x出现的次数{bool bit1 = _bs1.test(x);bool bit2 = _bs2.test(x);if (!bit1 && !bit2) return 0;else if (!bit1 && bit2) return 1;else if (bit1 && !bit2) return 2;else return 3;}private:bitset<N> _bs1;bitset<N> _bs2;};
}

题目三:给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件的交集?

把数据读出来,分别放到两个位图,依次遍历,同时在两个位图的值就是交集

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 实践致知第17享:电脑忽然黑屏的常见原因及处理方法
  • linux perf
  • C# Unity 面向对象补全计划 七大原则 之 里氏替换(LSP) 难度:☆☆☆ 总结:子类可以当父类用,牛马是马,骡马也是马
  • 论文解读 | ACL 2024:自我蒸馏在语言模型微调中架起分布差异的桥梁
  • PyTorch深度学习实战(4)—— Tensor的基本操作
  • 锐捷RCNA | 远程登录与路由技术
  • Python获取Excel内容
  • 用Manim计算和可视化某个函数图的微分切割线
  • 网站或者网页Cookie 启用说明
  • 成都云飞浩容文化传媒有限公司共绘电商服务新蓝图
  • Mistral AI:欧洲AI新星的崛起之路
  • 笔记:Java生产环境服务器卡顿排查
  • AppBoot:像 Django 一样使用 FastAPI
  • 记录|如何统一管理多个同一个对象?
  • Apache Kylin 系列入门教程
  • 【css3】浏览器内核及其兼容性
  • 【技术性】Search知识
  • 10个确保微服务与容器安全的最佳实践
  • go append函数以及写入
  • js 实现textarea输入字数提示
  • KMP算法及优化
  • React系列之 Redux 架构模式
  • Redis的resp协议
  • Redis中的lru算法实现
  • Stream流与Lambda表达式(三) 静态工厂类Collectors
  • 编写符合Python风格的对象
  • 从setTimeout-setInterval看JS线程
  • 离散点最小(凸)包围边界查找
  • 思维导图—你不知道的JavaScript中卷
  • 通过npm或yarn自动生成vue组件
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • # Swust 12th acm 邀请赛# [ E ] 01 String [题解]
  • #C++ 智能指针 std::unique_ptr 、std::shared_ptr 和 std::weak_ptr
  • #FPGA(基础知识)
  • #Linux杂记--将Python3的源码编译为.so文件方法与Linux环境下的交叉编译方法
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • (007)XHTML文档之标题——h1~h6
  • (02)Unity使用在线AI大模型(调用Python)
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (160)时序收敛--->(10)时序收敛十
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (39)STM32——FLASH闪存
  • (6)添加vue-cookie
  • (NO.00004)iOS实现打砖块游戏(十二):伸缩自如,我是如意金箍棒(上)!
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (过滤器)Filter和(监听器)listener
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (面试必看!)锁策略
  • (四)React组件、useState、组件样式
  • (一)Mocha源码阅读: 项目结构及命令行启动
  • (转)fock函数详解
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • (自用)网络编程
  • .aanva