【C++】位图/布隆过滤器+海量数据处理

目录

一、位图

1.1 位图的概念

1.2 位图的实现

1.3 位图的应用(面试题)

二、布隆过滤器

2.1 布隆过滤器的引入

2.2 布隆过滤器概念

2.3 布隆过滤器的插入和查找

2.4 布隆过滤器的实现

2.5 布隆过滤器的优点和缺陷

2.6 布隆过滤器的应用(面试题)


一、位图

1.1 位图的概念

在C++中,位图(Bitmap)是一种数组,其中数组的每个元素可以表示多个布尔值。用于高效地存储和查询海量数据,其中元素的每个布尔值只占用一个位(bit)的空间。通常是用来判断某个数据存不存在的。

例如在32位系统中,一个unsigned int类型的变量可以表示32个布尔值。通过位操作,我们可以检查、设置或清除特定的位,从而实现对大量布尔值的快速访问和修改。

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

方法:

1. 遍历,时间复杂度O(N)
2. 排序(O(NlogN)),利用二分查找: logN
3. 位图解决

方法1和2中都需要将数据保存在数组中,40亿个整数需要16G内存,内存开辟不了这么大的连续空间。
数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一
个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0
代表不存在。
比如:

40亿个不重复的无符号整数的范围在0 ~ 2^32,有42亿多种可能性。一个字符型8个比特位,每个比特位来存储一个数字是否存在的状态,需要(2^32) / 8 = 2^29字节,即0.5G的内存。

1.2 位图的实现

C++标准库中的位图在线文档说明     #include <bitset>

在C++中,位图的实现通常使用unsigned int数组或者std::vector<unsigned int>。下面是一个简单的位图实现:

// N是需要多少比特位
template<size_t N>
class myBitSet
{
public:myBitSet(){_bs.resize((N >> 5) + 1, 0);//一个size_t 4个字节,32个比特位,右移五位相当于除以32,+1是防止有余数//注意:右移运算符>>优先级高于+,需要在右移时加上括号}void set(size_t x)//对第x个比特位置为1{size_t i = x / 32;//确定在哪一个size_tsize_t j = x % 32;//确定在哪一个比特位上_bs[i] |= (1 << j);}void reset(size_t x)//第x个比特位置清0{size_t i = x / 32;//确定在哪一个size_tsize_t j = x % 32;//确定在哪一个比特位上_bs[i] &= ~(1 << j);}bool test(size_t x)//返回第x位的状态{size_t i = x / 32;//确定在哪一个size_tsize_t j = x % 32;//确定在哪一个比特位上return _bs[i] & (1 << j);}private:vector<size_t> _bs;
};

1.3 位图的应用(面试题)

1. 快速查找某个数据是否在一个集合中
2. 排序 + 去重
3. 求两个集合的交集、并集等
4. 操作系统中磁盘块标记

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

100一个整数,它的范围也是只在0 ~ 2^32 之间,可以设计两个位图,每一次映射都调整位图里面的数字。例如出现0次设为00,出现1次设为01,二次及以上都为10。

template<size_t N>
class twobitset
{
public:void set(size_t x){//00->01//01->10//10->不变if (_bs1.test(x) == false && _bs2.test(x) == false)//出现0次{_bs2.set(x);//新增设为01}else if (_bs1.test(x) == false && _bs2.test(x) == true)//出现1次{_bs1.set(x);_bs2.reset(x);//新增设为10}//出现2次及以上}void PrintOnce(){for (size_t i = 0; i < N; i++){if (_bs1.test(i) == false && _bs2.test(i) == true){cout << i << endl;}}cout << endl;}private:myBitSet<N> _bs1;myBitSet<N> _bs2;
};

2. 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?

各自映射到一个位图,若一个值在两个位图都存在,则是交集

3. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数。

方法同问题1,创建两个位图,出现0次00,出现1次01,出现2次10,出现3次及以上 11。

二、布隆过滤器

2.1 布隆过滤器的引入

之前的搜索方法中,传统的数据结构和方法存在一定的局限性:

  • 暴力查找:数据量大了,效率就低。O(n)
  • 排序+二分查找:排序有代价、数组不方便增删。虽然查找的时间复杂度为 O(log n),但排序的时间复杂度为 O(n log n)
  • 搜索树 ->AVL树+红黑树:随着数据量的增加,树的高度也会增加,导致性能下降。
  • 哈希:当发生哈希碰撞时,性能会下降。此外,哈希表的空间消耗相对较高。
  • 以上数据结构,空间消耗很高。如何应对数量很大的数据?
  • 位图及变形:位图只能处理整数,对于其他类型的数据则不适用。

如果要应对其他类型数据的问题,如何快速查找?

  • 用哈希表存储用户记录,缺点:浪费空间
  • 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。
  • 将哈希与位图结合,即布隆过滤器。

2.2 布隆过滤器概念

布隆过滤器(Bloom Filter)是一种由布隆在1970年提出的概率型数据结构,它通过使用多个哈希函数将元素映射到一个 bit 数组中来实现高效的数据查询和插入。布隆过滤器的核心优势在于它的空间效率:它使用相对较少的内存来表示一个可能非常大的集合。可以用来告诉你 “某样东西 一定不存在 或者 可能存在”。

布隆过滤器的特点包括:

  • 高效插入和查询:插入和查询操作都非常快速,因为它们只涉及到哈希计算和位操作。
  • 概率性:布隆过滤器可能会返回误报(即一个元素被错误地判断为存在于集合中),但不会返回漏报(即如果一个元素被判断为不存在,那么它一定不存在)。
  • 固定大小:布隆过滤器的位数组大小在创建时确定,不会随着元素的添加而改变。
  • 不可删除:一旦元素被插入布隆过滤器,就无法删除,因为删除可能会导致其他元素的位被错误地设置为0。(传统布隆过滤器不可删除)

举个例子:原本在位图或哈希表/桶中,可能出现多个关键字映射到同一个位置,现在使用多个哈希函数,让原本映射到一个位置的结果映射到多个位置。检查关键字在不在,要把关键字映射的所有位置都检查一遍,只有所有位置的状态都为1,这个关键字才 “可能存在”,如果映射的位置有一个检测到了0,那么这个关键字肯定不存在。

详解布隆过滤器的原理,使用场景和注意事项

上面链接的讲解非常精彩,有兴趣的话可以看一下。

2.3 布隆过滤器的插入和查找

布隆过滤器是一个 bit 向量或者说 bit 数组

如果我们要映射一个值到布隆过滤器中,我们需要使用多个不同的哈希函数生成多个哈希值,并对每个生成的哈希值指向的 bit 位置为 1,例如针对值 “baidu” 和三个不同的哈希函数分别生成了哈希值 1、4、7,则上图转变为:

在再存一个值 “tencent”,如果哈希函数返回 3、4、8 的话,图继续变为:

值得注意的是,4 这个 bit 位由于两个值的哈希函数都返回了这个 bit 位,因此它被覆盖了。现在我们如果想查询 “dianping” 这个值是否存在,哈希函数返回了 1、5、8三个值,结果我们发现 5 这个 bit 位上的值为 0,说明没有任何一个值映射到这个 bit 位上,因此我们可以很确定地说 “dianping” 这个值不存在。而当我们需要查询 “baidu” 这个值是否存在的话,那么哈希函数必然会返回 1、4、7,然后我们检查发现这三个 bit 位上的值均为 1,那么我们可以说 “baidu” 存在了么?答案是不可以,只能是 “baidu” 这个值可能存在。

这是为什么呢?答案跟简单,因为随着增加的值越来越多,被置为 1 的 bit 位也会越来越多,这样某个值 “taobao” 即使没有被存储过,但是万一哈希函数返回的三个 bit 位都被其他值置位了 1 ,那么程序还是会判断 “taobao” 这个值存在。

2.4 布隆过滤器的实现

各种字符串Hash函数

选取3种平均分较高的哈希算法:BKDRHash,APHash,DJBHash。

#include "myBitSet.h"struct BKDRHash
{size_t operator()(const string& key){// BKDRsize_t hash = 0;for (auto e : key){hash *= 31;hash += e;}return hash;}
};struct APHash
{size_t operator()(const string& key){size_t hash = 0;for (size_t i = 0; i < key.size(); i++){char ch = key[i];if ((i & 1) == 0){hash ^= ((hash << 7) ^ ch ^ (hash >> 3));}else{hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));}}return hash;}
};struct DJBHash
{size_t operator()(const string& key){size_t hash = 5381;for (auto ch : key){hash += (hash << 5) + ch;}return hash;}
};template <size_t N, class K = string, class HashFunc1 = BKDRHash, class HashFunc2 = APHash, class HashFunc3 = DJBHash>
class myBloomFilter
{
public:void set(const K& key){size_t hash1 = HashFunc1()(key) % N;size_t hash2 = HashFunc2()(key) % N;size_t hash3 = HashFunc3()(key) % N;_bs.set(hash1);_bs.set(hash2);_bs.set(hash3);}// 一般不支持删除,删除一个值可能会影响其他值// 非要支持删除,也是可以的,用多个位标记一个值,存引用计数// 但是这样话,空间消耗的就变大了void reset(const K& key);bool test(const K& key){//判断不存在是准确的size_t hash1 = HashFunc1()(key) % N;if (_bs.test(hash1) == false)return false;size_t hash2 = HashFunc2()(key) % N;if (_bs.test(hash2) == false)return false;size_t hash3 = HashFunc3()(key) % N;if (_bs.test(hash3) == false)return false;return true;//有可能误判}private:myBitSet<N> _bs;
};

2.5 布隆过滤器的优点和缺陷

布隆过滤器优点
1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
2. 哈希函数相互之间没有关系,方便硬件并行运算
3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

布隆过滤器缺陷
1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
2. 不能获取元素本身
3. 一般情况下不能从布隆过滤器中删除元素
4. 如果采用计数方式删除,可能会存在计数回绕问题

2.6 布隆过滤器的应用(面试题)

布隆过滤器使用场景:可以接受误判的场景

1. 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法。

query 是字符串,假设平均1个query是50byte,1G 约等于10亿byte,100亿个query就是5000亿byte ,约等于 500G,内存根本存不下。

500G的文件存放不下,可以将它们拆分成多个小文件,例如500个小文件a0~a499,每个文件大小就是1G。读取第一个文件的每个query,使用hash函数将query映射到500个文件中(计算 i = Hash(query) % 500),i 是几,query就进入第几个小文件。

两个文件中相同的query分别进入了编号相同的小文件 ai 和 bi。

两个大文件对应的编号相同的小文件ai 和 bi求交集,可以分别插入到一个setA和一个setB中,快速找到交集。

但是这样也可能有其他问题,例如某个文件可能太大,原因:

  1. 小文件中大多数都是某一个query,重复过多。
  2. 小文件有很多不同的query。

如果是情况1,文件很大有很多重复,后面重复播入都失败,可以插入到set,set可以去重。如果是情况2,不断插入set以后,内存不足,会抛异常,需要换一个哈希函数进行二次切分,再找交集。

近似算法(布隆过滤器)
1. 创建布隆过滤器:由于1G内存大约可以表示10亿字节,即约80亿bit,我们可以创建一个布隆过滤器,使用足够多的哈希函数(例如4到6个),以保持较低的误报率。

2. 插入第一个文件的query:遍历第一个文件中的每个query,对每个query应用哈希函数,并将对应位设置为1。

3. 查询第二个文件的query:遍历第二个文件中的每个query,同样应用哈希函数。如果布隆过滤器中的所有对应位都是1,则认为该query可能出现在第一个文件中。

4. 输出可能交集:将所有可能出现在交集中的query输出到新文件中。

5. 误报处理:由于布隆过滤器的性质,输出文件中可能包含一些误报,即实际上并不在第一个文件中的query。可以通过调整布隆过滤器的参数来减少误报率。

精确算法(哈希分割)
1文件分割:对两个文件的query进行哈希处理,然后使用除留余数法(%5000)将query分布到5000个不同的子文件中。每个子文件的大小大约为100M,这样每个文件的总大小就是5000 * 100M = 500G,符合原始数据的大小。

2.读取和比较:对于第二个文件中的每个query,同样进行哈希处理,然后将query放入对应的子文件中。接着,对于每个子文件,将其内容读取到内存中的unordered_map中,然后查找第一个文件中的对应子文件,看是否有相同的query。
3.输出交集:如果在一个子文件中找到了匹配的query,那么这个query就属于两个文件的交集。将这些query输出到新文件中。
4.内存使用:由于每个子文件是独立处理的,所以每次只需要将一个子文件的内容读取到内存中,这样内存的使用就被限制在了100M左右,符合1G内存的限制。
精确算法的关键在于将大文件分割成多个小文件,然后逐个处理,这样可以避免一次性将所有数据加载到内存中。这种方法虽然准确,但是处理时间可能会比近似算法长,因为需要多次读写磁盘。

2. 给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?

与上一题同理,i= Hash(ip) % 100,这个ip就进入Ai小文件。相同的ip一定进入了同一个小文件,我们对小文件用map统计出ip次数就是准确的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/13747.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Servlet 的 API

HttpServlet init&#xff1a;当 tomcat 收到了 /hello 这样的路径是请求后就会调用 HelloServlet&#xff0c;于是就需要对 HelloServlet 进行实例化&#xff08;只实例一次&#xff0c;后续再有请求也不实例了&#xff09;。 destory&#xff1a;如果是通过 smart tomcat 的停…

洗地机品牌哪个牌子好?实力派洗地机品牌TOP10榜单

洗地机依靠其洗、拖、吸、烘为一体的功能&#xff0c;能高效的完成地面清洁的工作&#xff0c;深受大家的喜爱。但是洗地机的型号越来越多&#xff0c;功能也越来越多&#xff0c;对于不想花大价钱&#xff0c;又想要高性价比的精致人群来说实在不友好&#xff0c;所以笔者今天…

C++ 中重写重载和隐藏的区别

重写&#xff08;override&#xff09;、重载&#xff08;overload&#xff09;和隐藏&#xff08;overwrite&#xff09;在C中是3个完全不同的概念。我们这里对其进行详细的说明 1、重写&#xff08;override&#xff09;是指派生类覆盖了基类的虚函数&#xff0c;这里的覆盖必…

实例展示vue单元测试及难题解惑

通过生动详实的例子带你排遍vue单元测试过程中的所有疑惑与难题。 技术栈&#xff1a;jest、vue-test-utils。 共四个部分&#xff1a;运行时、Mock、Stub、Configuring和CLI。 运行时 在跑测试用例时&#xff0c;大家的第一个绊脚石肯定是各种undifned报错。 解决这些报错…

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(九)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 16 节&#xff09; P16《15.ArkUI-状态管理-任务统计案例》 1、实现任务进度卡片 怎么让进度条和进度展示文本堆叠展示&#xff1…

./scripts/Makefile.clean 文件分析

文章目录 目标 $(subdir-ymn)目标__clean $(clean-dirs):     make -f ./scripts/Makefile.clean obj$(patsubst _clean_%,%,$) $(clean-dirs)$(patsubst _clean_%,%,$)_clean_api _clean_cmd _clean_common _clean_disk _clean_drivers _clean_drivers/ddr/altera _clean_d…

react中的useEffect()的使用

useEffect()是react中的hook函数&#xff0c;作用是用于创建由渲染本身引起的操作&#xff0c;而不是事件的触发&#xff0c;比如Ajax请求&#xff0c;DOM的更改 首先useEffect()是个函数&#xff0c;接受两个参数&#xff0c;第一个参数是一个方法&#xff0c;第二个参数是数…

vue3 路由跳转 携带参数

实现功能&#xff1a;页面A 跳转到 页面B&#xff0c;携带参数 路由router.ts import { createRouter, createWebHistory } from "vue-router";const routes: RouteRecordRaw[] [{path: "/demo/a",name: "aa",component: () > import(&quo…

x264 码率控制原理:x264_ratecontrol_start 函数

x264_ratecontrol_start 函数 函数原理 函数功能:编码一帧之前,为当前帧选择一个量化 QP,属于帧级别码率控制;这对于控制视频质量和文件大小至关重要。通过调整QP,编码器可以在保持视频质量的同时,尽可能减小输出文件的大小。函数参数:x264_t *h: 编码器上下文结构体指…

地信遥感测绘电子书

《地理信息系统概论》&#xff0c;黄杏元&#xff0c;马劲松编著&#xff0c;第三版&#xff0c;高等教育出版社&#xff0c;2008年 《地理信息系统》&#xff08;第二版&#xff09;汤国安&#xff0c;赵牡丹&#xff0c;杨昕等编&#xff0c;高等教育出版社&#xff0c;2010…

【stm32/CubeMX、HAL库】嵌入式实验五:定时器(2)|PWM输出

参考&#xff1a; 【【正点原子】手把手教你学STM32CubeIDE开发】 https://www.bilibili.com/video/BV1Wp42127Cx/?p13&share_sourcecopy_web&vd_source9332b8fc5ea8d349a54c3989f6189fd3 《嵌入式系统基础与实践》刘黎明等编著&#xff0c;第九章定时器&#xff0c…

8操作系统定义、分类及功能+设备管理+作业管理 软设刷题 软考+

操作系统定义、分类及功能设备管理作业管理 知识点1-55-1010-1515-2020-2525-3030-35 刷题操作系统定义、分类及功能1-55-1010-15作业管理1-5设备管理1-55-10 知识点 1-5 1 嵌入式操作系统的特点&#xff1a; 1.微型化&#xff0c;从性能和成本角度考虑&#xff0c;希望占用的…

145.栈和队列:删除字符串中的所有相邻重复项(力扣)

题目描述 代码解决 class Solution { public:string removeDuplicates(string s) {// 定义一个栈来存储字符stack<char> st;// 遍历字符串中的每一个字符for(int i 0; i < s.size(); i){// 如果栈为空或栈顶字符与当前字符不相同&#xff0c;则将当前字符入栈if(st.e…

Jenkins的Pipeline流水线

目录 前言 流水线概念 什么是流水线 Jenkins流水线 pipeline node stage step 创建一个简单的流水线 创建Pipeline项目 选择模板 测试 前言 提到 CI 工具&#xff0c;首先想到的就是“CI 界”的大佬——Jenkjns,虽然在云原生爆发的年代,蹦出来了很多云原生的 CI 工具…

Hive的窗口函数

定义&#xff1a; 聚合函数是针对定义的行集(组)执行聚集,每组只返回一个值.如sum()、avg()、max() 窗口函数也是针对定义的行集(组)执行聚集,可为每组返回多个值.如既要显示聚集前的数据,又要显示聚集后的数据.步骤&#xff1a; 1.将记录分割成多个分区. 2.在各个分区上调用窗…

word-表格疑难杂症诊治

一、用表格进行排版图片、制作公文头 可以在插入图片时固定列宽 二、表格中的疑难杂症 问题一&#xff1a;表格超过页面&#xff0c;右侧文字看不见 解决&#xff1a;表格窗口-布局-自动调整-根据窗口自动调整表格 问题二&#xff1a;表格底部文字被遮挡 解决&#xff1a;布…

【linux】yumvim工具理解使用

目录 Linux 软件包管理器 yum 关于 rzsz 注意事项 查看软件包 Linux开发工具 Linux编辑器-vim使用 vim的基本概念 vim的基本操作 vim正常模式命令集 vim末行模式命令集 简单vim配置 配置文件的位置 sudo提权 Linux 软件包管理器 yum 1.yum是什么&#xff1…

攻防世界---web---warmup

1、题目描述 2、查看源码&#xff0c;发现有个source.php 3、访问该文件&#xff0c;得到这一串代码 4、分析代码 5、访问hint.php&#xff0c;提示flag在ffffllllaaaagggg这个文件下 6、构造payload ?filesource.php?/../../../../../../ffffllllaaaagggg

Fitting Parameterized Three-Dimensional Models to Images

摘要 基于模型的识别和运动跟踪依赖于解决投影和模型参数&#xff0c;使其最佳适应匹配的2D图像特征的3D模型的能力。本文将当前的参数求解方法扩展到处理具有任意曲面和任意数量的内部参数&#xff08;表示关节、可变尺寸或表面变形&#xff09;的对象。开发了数值稳定化方法…

懒人网址导航页 search.html SQL注入漏洞复现

0x01 产品简介 懒人网址导航系统是一种智能化的网址导航平台,旨在帮助用户快速找到所需的网址和资源。该系统提供了智能化的网址搜索和推荐功能,能够根据用户的搜索习惯和偏好推荐相关的网址和资源。同时,系统还提供了网址分类、网址收藏和网址分享等功能,方便用户管理和共…