位图及布隆过滤器的模拟实现与面试题

news/2024/10/25 15:27:26/

位图

模拟实现

namespace yyq
{template<size_t N>class bitset{public:bitset(){_bits.resize(N / 8 + 1, 0);//_bits.resize((N >> 3) + 1, 0);}void set(size_t x)//将某位做标记{size_t i = x / 8; //第几个char对象size_t j = x % 8; //这个char对象的第几个比特位_bits[i] |= (1 << j); //标记}void reset(size_t x)//将某位去掉标记{size_t i = x / 8;size_t j = x % 8;_bits[i] &= (~(1 << j));}//测试值是否在bool test(size_t x){size_t i = x / 8;size_t j = x % 8;return _bits[i] & (1 << j);//整型提升,bool是4字节,char是1字节,按符号位来补}private:std::vector<char> _bits;};
}

当然位图也有缺点,它只能处理整型数据。

应用

  1. 快速查找某个数据是否在一个集合中
  2. 排序 + 去重
  3. 求两个集合的交集、并集等
  4. 操作系统中磁盘块标记
    位图,是利用一个比特位来标识数据在不在(哈希的直接地址法),优点是节省空间,效率高,缺点是只能处理整型数据且要求数据相对集中。将哈希与位图结合,即布隆过滤器。

位图是要把一个数据通过一个哈希函数映射到一个位置,判断在不在;布隆过滤器是要把一个数据通过多个哈希函数映射到多个位置,降低误判率,判断一定不在或可能在

布隆过滤器

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。

模拟实现

哈希函数个数的选择

哈希函数个数越多,布隆过滤器要开的bit位就越多,内存占用更大,则布隆过滤器bit位置为1的速度越快,但是效率变低;个数过少的话,误报率会变高。

k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率

计算公式为k=m/n∗ln(2)k = m / n * ln(2)k=m/nln(2)以及m=−n∗ln(p)/ln2/ln2m = -n*ln(p) / ln2 / ln2m=nln(p)/ln2/ln2

第一个公式可以得出m=k∗n/ln2m = k * n / ln2m=kn/ln2,当我们用3个哈希函数时,布隆过滤器的长度为3∗n/ln2≈4.33n3*n/ln2 ≈ 4.33n3n/ln24.33n

在代码中,我们直接取5n,代码中为X == 5,可以更改。

struct BKDRHashFunc
{size_t operator()(const std::string& key){size_t hash = 0;for (auto ch : key){hash *= 131;hash += ch;}return hash;}
};struct APHashFunc
{size_t operator()(const std::string& key){size_t hash = 0;const char* str = key.c_str();for (int i = 0; *str; i++){if ((i & 1) == 0){hash ^= ((hash << 7) ^ (*str++) ^ (hash >> 3));}else{hash ^= (~(hash << 11) ^ (*str++) ^ (hash >> 5));}}return hash;}
};struct DJBHashFunc
{size_t operator()(const std::string& key){size_t hash = 5381;const char* str = key.c_str();while (*str){hash += (hash << 5) + (*str++);}return hash;}
};// N是最多存储的数据个数
// 平均存储一个值,开辟X个位
template<size_t N, size_t X = 5, class K = std::string, class HashFunc1 = BKDRHashFunc, class HashFunc2 = APHashFunc, class HashFunc3 = DJBHashFunc>
class BloomFilter
{public:void set(const K& key){//3个哈希函数映射size_t hashi1 = HashFunc1()(key) % (X * N);size_t hashi2 = HashFunc2()(key) % (X * N);size_t hashi3 = HashFunc3()(key) % (X * N);_bs.set(hashi1);_bs.set(hashi2);_bs.set(hashi3);}bool test(const K& key){//3个哈希函数映射size_t hashi1 = HashFunc1()(key) % (X * N);if (!_bs.test(hashi1)){//如果通过一个映射值不在,那肯定不在return false;}size_t hashi2 = HashFunc2()(key) % (X * N);if (!_bs.test(hashi1)){//如果通过一个映射值不在,那肯定不在return false;}size_t hashi3 = HashFunc3()(key) % (X * N);if (!_bs.test(hashi1)){//如果通过一个映射值不在,那肯定不在return false;}//前三个映射值都存在,那么key可能在(有可能三个位置都冲突)return true;}private:std::bitset<N * X> _bs;
};

测试误判率

void test_bloomfilter2()
{srand(time(0));const size_t N = 100000;BloomFilter<N> bf;std::vector<std::string> v1;std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";for (size_t i = 0; i < N; ++i){v1.push_back(url + std::to_string(i));}for (auto& str : v1){bf.set(str);}// v2跟v1是相似字符串集,但是不一样std::vector<std::string> v2;for (size_t i = 0; i < N; ++i){std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";url += std::to_string(999999 + i);v2.push_back(url);}size_t n2 = 0;for (auto& str : v2){if (bf.test(str)){++n2;}}std::cout << "相似字符串误判率:" << (double)n2 / (double)N << std::endl;// 不相似字符串集std::vector<std::string> v3;for (size_t i = 0; i < N; ++i){std::string url = "zhihu.com";url += std::to_string(i + rand());v3.push_back(url);}size_t n3 = 0;for (auto& str : v3){if (bf.test(str)){++n3;}}std::cout << "不相似字符串误判率:" << (double)n3 / (double)N << std::endl;
}

不支持reset

因为某一位可能被多个值映射,有冲突。把这个位reset掉,可能导致真的在的key就变成不在了。

面试题

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

位图要完成的事情是在不在,只需要2种状态==>1个比特位,char的8个比特位可以表示8个数的状态。而这道题需要3种状态(0:00、1:01、n:10)==>2个比特位,char的8个比特位可以表示4个数的状态。

开两个位图,两个位图的相同的位置可以用0和1表示,当这个数出现第1次,第一个位图对应位置置1;第2次及以上次出现,第2个位图对应位置置1。

要筛选出现1次的整数,就用2个位图;要筛选出现2次的整数,就用3个位图,以此类推。

	template<size_t N>class twobitset{public:void set(size_t x)//将某位做标记{			if (!_bits1.test(x) && !_bits2.test(x))//00{_bits2.set(x);}else if (!_bits1.test(x) && _bits2.test(x))//01{_bits2.reset(x);_bits1.set(x); //10}else//10{//啥也不做}}private:std::bitset<N> _bits1;std::bitset<N> _bits2;};
}

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

两个文件的话,每个文件分别使用一个位图,此时位图对应的功能就包括去重+交集。两个位图位置都为1,就是两个文件的交集。

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

int的最大值为24亿多,找不超过两次的,要用到2个位图4种状态(00\01\10\11),然后要过滤掉00和11这两个状态对应的数据

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

ip是这样的127.0.0.1一个字符串。位图只能解决K问题(在不在),不能解决KV问题(多少次)。这里要求出现次数最多的,只能采用map来解决问题,100G大小肯定放不进去内存,我们利用哈希切割,先将文件分为100个小文件(注意不是平均分割),将每个小文件当作一个哈希桶,用函数将ip转成整型,i = HashFunc(ip) % 100,i冲突的ip就会进入对应i号文件,那同一类ip就会进入同一个文件(相同的值一定会进入同一个文件,当然也会有哈希冲突的值),再对每个文件进行map统计出现次数。

如果:单个小文件超过1G,说明这个小文件里冲突的ip很多,a.大多是不同的ip/b.大多是相同的ip,该如何处理?

a.大多是不同的ip的情况,用map肯定无法完全统计,换个字符串哈希转换函数,递归再切分。

b.大多是相同的ip的情况,用map可以统计,大不了再用外排序。

如果map的insert失败,就表示没有内存了,相当于new节点失败,new失败会抛异常,就按a来处理。

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

query是查询指令,比如可能是一个网页请求或者是一个数据库sql语句。

精确算法:假设每个query指令是50字节,那100亿个query大小约为500GB。将这些数据分到1000个小文件(Axx、Bxx),每个文件约0.5GB。每个小文件是通过同一个哈希函数,对应编号的文件里的数据大多是差不多的,把数据去个重,然后A01和B01分别用哈希表求交集,…A99和B99分别求交集。若小文件超过1GB,就再换个哈希函数再切分。

近似算法:用布隆过滤器,先把一个文件过一遍布隆过滤器,另一个文件来判断一下有哪些在。

6、如何扩展BloomFilter使得它支持删除元素的操作

计数器,有几个值映射到这个位,这个位就是几,当要求reset时,这个位置的值–。但是要实现计数的功能,映射位置就不能再使用一个位标记,而是需要多个位存储计数值,空间消耗成倍增加。故此方案在实际中不会被使用,还不如用哈希表。


http://www.ppmy.cn/news/34923.html

相关文章

常见的js加密/js解密方法

常见的js加密/js解密方法 当今互联网世界中&#xff0c;数据安全是至关重要的。为了保护用户的隐私和保密信息&#xff0c;开发人员必须采取适当的安全措施。在前端开发中&#xff0c;加密和解密技术是一种常见的数据安全措施&#xff0c;其中 JavaScript 是最常用的语言之一。…

近万字文全面解读GPT-4,带你了解GPT-4

资料来源&#xff1a; GPT 4官网文章&#xff1a;https://openai.com/research/gpt-4 GPT-4 论文&#xff1a;https://cdn.openai.com/papers/gpt-4.pdf GPT-4 ChatGPT Plus&#xff1a;https://chat.openai.com/chat 申请GPT-4 API &#xff1a;https://openai.com/waitlist/g…

【数据结构】并查集

目录 一&#xff1a;用途 二&#xff1a;实现 O(1) 三&#xff1a;例题 例题1&#xff1a;集合 例题2&#xff1a;连通图无向 例题3&#xff1a;acwing 240 食物链 一&#xff1a;用途 将两个集合合并询问两个元素是否在一个集合当中 二&#xff1a;实现 O(1) 每…

ADT75温度模块---专业版调试器

所需设备&#xff1a; 1、USB转SPI_I2C适配器(专业版); 2、ADT75 温度模块&#xff1b; 概述&#xff1a; 12位温度-数字转换器B级精度1.0C&#xff08;0C至70C&#xff09;A级精度2.0C&#xff08;–25C至100C&#xff09;SMBus/I2C兼容接口工作温度范围&#xff1a;−55…

Metasploit详细教程

第一步&#xff1a;安装和启动Metasploit 您可以从Metasploit官方网站下载适用于您操作系统的Metasploit框架。安装Metasploit框架后&#xff0c;您可以使用以下命令来启动Metasploit&#xff1a; msfconsole该命令将启动Metasploit控制台。 第二步&#xff1a;查找目标设备…

c语言中的数组、数组名、指针的详解

在 C 语言中&#xff0c;数组名可以被视为一个指向数组第一个元素的指针。下面是一些关于 C 语言中数组名的详细解释&#xff1a; 1.数组名是一个指针常量 在 C 语言中&#xff0c;数组名是一个指向数组第一个元素的指针常量&#xff0c;也就是说&#xff0c;它存储的是数组第…

多线程控制讲解与代码实现

多线程控制 回顾一下线程的概念 线程是CPU调度的基本单位&#xff0c;进程是承担分配系统资源的基本单位。linux在设计上并没有给线程专门设计数据结构&#xff0c;而是直接复用PCB的数据结构。每个新线程&#xff08;task_struct{}中有个指针都指向虚拟内存mm_struct结构&am…

内存操作函数

前言 &#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f; c语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:介绍c语言中有关内存操作函数的知识. 金句分享: ✨未…