news 2026/10/28 1:57:25

哈希扩展 -- 位图思想

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
哈希扩展 -- 位图思想

试想一下:我们该如何在40亿个整数中判断一个数是否存在呢?

如果一个一个去遍历那就太慢了,时间复杂度就是O(n^2),直接pass掉;

如果先排序再二分,那排序的代价也是巨大的

如果能想到哈希表,那样的话要的空间太大,要4 * 40亿个字节的空间(2 ^ 10 = 1024,2 ^ 30 = 1024 * 1024 * 1024 ≈ 10亿,就是要2 ^ 34 个字节,1 GB = 1024 MB = 1024 * 1024 KB = 1024*1024*1024Byte(字节),也就是要差不多16个GB),堆上没有那么大的空间,但是已经很接近答案了;

如果我们只让一个比特位就代表一个整数呢?

这就是我们的位图了

位图

位图是什么?

我们都知道一个字节 = 8个比特位,一个整型就是32个比特位

先假设我们要在0 ~ 30中判断一个数是否存在,我们可以让一个bit位代表一个数是否存在(1代表存在,0代表不存在),这样我们每遍历一个数(假设是n)我们就将那个数所代表的bit位设置成1(bit_set |= (1 << n)),最后我们想要知道一个数存不存在(假设是k),我们只需要判断第k位是0还是1即可,这样我们就用O(1)的空间复杂度和O(n)的时间复杂度就解决了这个问题

#inlcude<iostream> using namespace std; int main() { srand(time(nullptr));// 设置种子 vector<int> v(30); for (int i = 0; i < 30; i++)// 插入随机数 v.push_back(rand() % 31); int bit_set = 0; for (auto& i : v)// 设置bit_set的bit位,并打印数组 { cout << i << ' '; bit_set |= (1 << i); } cout << endl; for (int i = 0; i <= 30; i++)// 判断 0 ~ 30 里哪些数存在 { if (bit_set & (1 << i)) cout << i << "存在" << endl; else cout << i << "不存在" << endl; } return 0; }

那么这种利用每个bit位代表一个数字的思想,就叫作位图

位图本质上就是一个数组,但是这个数组只存1和0

既然这样我们用位图的思想解决上面的问题不就能大大减少时间和空间了嘛!

位图的实现模拟

位图类的实现主要看:构造函数,设置1,设置0,判断是否存在

#include<iostream> #include<vector> using namespace std; // 设置位图能判断0 ~ N个数 template<size_t N> class bit_set { // 用vector储存位图数组 vector<int> _bs; public: // 构造函数 bit_set()// 设置vector大小 { int size = (N / 32) + 1; _bs.resize(size); } //设置1 void set(int n) { int i = n / 32;// 判断n在第几个位图 int j = n % 32;// n在该位图的第几位 _bs[i] |= 1 << j; } void reset(int n) { int i = n / 32; int j = n % 32; _bs[i] &= (~(1 << j));// 注意运算符优先级 } // 判断 bool test(int n) { int i = n / 32; int j = n % 32; return _bs[i] & (1 << j); } }; void test_bitset1() { int a1[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6 }; int a2[] = { 5,3,5,99,6,99,33,66 }; bit_set<100> bs1; bit_set<100> bs2; for (auto e : a1) { bs1.set(e); } for (auto e : a2) { bs2.set(e); } for (size_t i = 0; i < 100; i++) { if (bs1.test(i) && bs2.test(i)) { cout << i << endl; } } } int main() { test_bitset1(); return 0; }

那么对于上面那个问题我们只需要

bit_set<-1> bs;

即可解决:由于是无符号整数,-1就被认定为了整型最大值(2 ^ 32 ≈ 40亿)

当然位图也是一个数据结构,在STL库中有对应的容器bitset

位图应用实训

接下来我们看几道位图相关的考察题目:

1.如何在100亿个整数中找到只出现一次的整数?

我们还是用位图,但这里我们不是按整数数量开空间,我们是按照范围来开空间!

由于整数大小不超过2^32-1,根据鸽巢原理,这100亿个整数一定会有重复的数,所以不用担心空间不够的问题,所以还是开2^32的空间大小

然后我们要确定只出现一次的整数:我们可以再多创建一个位图,分别命名为bs1,bs2,然后同时来计数。

即:

该数的出现次数bs1的取值情况bs2的取值情况
0次00
一次01
两次10
三次及以上11

然后我们只要输出bs1为0,bs2为1的情况即可

2.给你两个文件,分别有100亿个整数,我们只有1GB内存,试找出两文件的交集

和上题一样,创造2个bit_set(bs1和bs2),但是不是统计数量,bs1记录文件1中数k的出现情况,bs2记录文件2中数k的出现情况

数k在文件1/2中的出现情况bs1的取值情况bs2的取值情况
文件1/2中均未出现00
仅在文件1中出现10
仅在文件2中出现01
文件1/2中均出现11

3.一个文件中有100亿个整数,1GB内存设计算法找出出现次数不超过2次的整数

这道题像是上面两道题的结合版,我们可以按照上面的思路来做;

我们也能复用STL库中的bitset设计一个类,来完成整个流程,我们只需调用函数即可:

#include<iostream> #include<bitset> using namespace std; template<size_t N> class twobitset { bitset<N> bs1; bitset<N> bs2; public: void Set(int n) { int bool1 = bs1.test(n); int bool2 = bs2.test(n); if (!bool1 && !bool2) // 00 -> 01 bs2.set(n); else if (!bool1 && bool2) // 01 -> 10 { bs1.set(n); bs2.reset(n); } else if (bool1 && !bool2) // 10 -> 11 bs2.set(n); } void Test(int n) { int bool1 = bs1.test(n); int bool2 = bs2.test(n); if (!bool1 && bool2)// 01 { cout << n << "出现了1次" << endl; } else if (bool1 && !bool2)// 10 { cout << n << "出现了2次" << endl; } } }; int main() { FILE* pf = fopen("test.txt", "w"); if (pf == nullptr) { cout << "打开文件错误!" << endl; return 1; } srand(time(nullptr)); for (int i = 0; i < 100; i++) fprintf(pf, "%d\n", rand() % 50 + 1); fclose(pf); pf = fopen("test.txt", "r"); if (pf == nullptr) { cout << "打开文件错误!" << endl; return 1; } int n = 0; twobitset<100> tbs; while (fscanf(pf, "%d", &n) == 1) tbs.Set(n); for (int i = 1; i <= 50; i++) { tbs.Test(i); } fclose(pf); return 0; }

上面具体的文件操作不做详细说明,感兴趣的可以看看这篇博客

C语言中的文件操作-CSDN博客

以上就是本期博客的所有内容,感谢阅读

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 1:57:07

微信小程序 scroll‑view 组件实战:实现纵向与横向滚动

前言 scroll‑view是微信小程序中非常重要的滚动视图组件&#xff0c;可以实现纵向滚动和横向滚动&#xff0c;很多轮播、长列表、横向滑动卡片都是基于该组件开发。本篇通过完整案例&#xff0c;带你掌握 scroll‑view 常用属性、滚动事件、自动定位到指定子元素&#xff0c;…

作者头像 李华
网站建设 2026/10/28 1:56:20

一份旅行手册,豆包做完,IGA Pages 一句话就分享出去

打开豆包工作&#xff0c;说一句“帮我做一份北京红叶旅行手册”&#xff0c;几分钟后&#xff0c;一份排版清爽、带时间轴、带预算表的网页就躺在你的文件夹里。可朋友想看&#xff1f;你得把 HTML 连同图片文件夹一起发过去&#xff0c;朋友们下载、解压、双击打开 —— 手机…

作者头像 李华
网站建设 2026/10/28 1:53:13

无人机机载部署踩坑实录:7款轻量化YOLO横向实测,精度与速度平衡方案全拆解

做无人机机载目标检测部署的,大概率都经历过这种两难: 算法侧丢过来一个YOLOv11s,说VisDrone上精度够了,结果往Jetson Nano上一跑,帧率不到10,画面卡顿连带飞控延迟,根本没法上天; 换成nano版吧,帧率倒是上去了,航拍小目标直接漏检一半,远处的行人和车辆基本看不见,…

作者头像 李华
网站建设 2026/10/28 1:53:13

深度拆解航拍小目标检测:为什么原生YOLO效果拉胯?从网络结构到损失函数的根因分析

做过无人机航拍目标检测的人,大概率都有过这种落差: 拿COCO预训练的YOLO模型跑普通场景效果挺好,一换到航拍数据就直接哑火。地面上的行人、车辆,在4K航拍画面里就是十几个像素的小点,漏检率高得离谱,要么检测不出来,要么框得歪歪扭扭,密集区域还一堆误检。调参调了半个…

作者头像 李华