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

C++之哈希 --- 哈希的应用(位图布隆过滤器)


一、位图

1.1 位图的基本概念

       在如今网络交通高度发达的时代,网购已经成为我们日常生活中的一部分。没当双11到来,各大平台都会迎来一次网购的高潮。这就会让服务器短时间内获得高达几十亿上百亿的数据,那我们该如何去处理这海量的数据呢?这里给大家上一个腾讯的面试题。

       面对如此海量的数据,我们传统的容器(如unordered_map,map,vector等)似乎都无法驾驭这如此庞大的数据量。那我们该如何解决这个问题呢?
        先给出两种常规的思路:

        第三种方法当然是采用我们的位图啦,那什么是位图呢?其又是如何解决这个问题的呢?

位图概念

        所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

位图解决该问题
       数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。

判断一个数是否存在,我们只用了一个比特位就可以判断,这大大降低了我们的内存消耗。原本40亿数据要占用16GB的空间。现在只需要0.5G的内存就能处理了。

2.2 位图的模拟实现

其实C++的库里也有对应的位图

这里我们主要实现3个功能:

  • set(size_t x) :将对应数字位置为1
  • reset(size_t x) :将对应数字位置清0
  • test(size_t x) :查找该数字是否存在

实现位图我们主要要解决的问题就是,处理好位置映射的问题,这里我用一张图来好好解释解释:

那么接下来直接上代码:

template<size_t N>  //需要多少比特位
	class bitmap {
	public:
		bitmap() 
		{
			_bits.resize((N >> 5) + 1, 0);
		}

		void set(size_t x)//将一位置为1
		{
			int i = x / 32;
			int j = x % 32;
			_bits[i] |= (1 << j);
		}

		void reset(size_t x)//将一位置为0
		{
			int i = x / 32;
			int j = x % 32;
			_bits[i] &= (~(1 << j));
		}

		bool test(size_t x)//查找该数字是否存在
		{
			if (x > N) return false;
			int i = x / 32;
			int j = x % 32;
			return (_bits[i]>>j)&1;
		}

		~bitmap()
		{}

	private:
		vector<int> _bits;
	};

二、布隆过滤器

2.1 布隆过滤器的基本概念

我们先来了解一下布隆过滤器的背景:

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

这里举一个具体的例子来说明布隆过滤器的插入操作:

要是我想查找香蕉在不在,只需要根据不同的hash算法,算出对应的红色位置是否都为1就可以了
同时根据这张图,要给大家传递的一个概念是:

可以发现香蕉和菠萝有一个映射位置重合了,那么也就是说在之后插入数据的过程中,有可能该数据的所有hash位置都与其他数据的hash位置重合,导致这个数据并没有加入到我们的布隆过滤器中。由此我们可以得出一个结论:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。

2.2 布隆过滤器的模拟实现

这块话不多说直接上代码(这里的布隆过滤器复用了刚刚的位图代码)

struct BKDRHash
	{
		size_t operator()(const string& s)
		{
			// BKDR
			size_t value = 0;
			for (auto ch : s)
			{
				value *= 31;
				value += ch;
			}
			return value;
		}
	};
	struct APHash
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (long i = 0; i < s.size(); i++)
			{
				if ((i & 1) == 0)
				{
					hash ^= ((hash << 7) ^ s[i] ^ (hash >> 3));
				}
				else
				{
					hash ^= (~((hash << 11) ^ s[i] ^ (hash >> 5)));
				}
			}
			return hash;
		}
	};

	struct DJBHash
	{
		size_t operator()(const string& s)
		{
			size_t hash = 5381;
			for (auto ch : s)
			{
				hash += (hash << 5) + ch;
			}
			return hash;
		}
	};

	template<size_t N ,class HashFunc1 = BKDRHash,class HashFunc2 = APHash,class HashFunc3 = DJBHash,class K = string>
	class BloomFilter
	{
	public:
		BloomFilter() {}

		void set(const K& key)
		{
			size_t index1 = HashFunc1()(key) % (_ratio * N);
			size_t index2 = HashFunc2()(key) % (_ratio * N);
			size_t index3 = HashFunc3()(key) % (_ratio * N);

			_bt.set(index1);
			_bt.set(index2);
			_bt.set(index3);
			
		}

		bool test(const K& key)
		{
			size_t index1 = HashFunc1()(key) % (_ratio * N);
			if (_bt.test(index1) == false) return false;
			size_t index2 = HashFunc2()(key) % (_ratio * N);
			if (_bt.test(index2) == false) return false;
			size_t index3 = HashFunc3()(key) % (_ratio * N);
			if (_bt.test(index3) == false) return false;

			return true;
		}

		~BloomFilter() {}
	private:
		const static size_t _ratio = 5;
		bitmap<_ratio * N> _bt;
	};

2.3 布隆过滤器的优缺点

优点:

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

缺点:

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再

    建立一个白名单,存储可能会误判的数据)

  2. 不能获取元素本身

  3. 一般情况下不能从布隆过滤器中删除元素(会影响其他数据)

  4. 如果采用计数方式删除,可能会存在计数回绕问题


http://www.kler.cn/a/318334.html

相关文章:

  • 微澜:用 OceanBase 搭建基于知识图谱的实时资讯流的应用实践
  • Vue2:组件
  • 基于标签相关性的多标签学习
  • 结构体是否包含特定类型的成员变量
  • 时间管理的三个痛点
  • C++ 数组与结构 编程练习
  • 【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(上)
  • 【软件测试】详解测试中常用的几种测试方法
  • 从更底层的角度理解网站的访问过程
  • 算法打卡:第十一章 图论part05
  • 关于Python升级以后脚本不能运行的问题
  • MongoDB-aggregate流式计算:去重操作
  • Linux下go环境安装、环境配置并执行第一个go程序
  • python多继承 - 子类指定父类
  • 【教程】鸿蒙ARKTS 打造数据驾驶舱---前序
  • 两数之和、三数之和、四数之和
  • 在mac中如何使python3作为默认版本
  • 用canvas画一个验证码
  • 从 Oracle 集群到单节点环境(详细记录一次数据迁移过程)之一:生产环境与目标服务器详情
  • 基于物联网的火灾报警器设计与实现(论文+源码)
  • 高维数据和超高维数据
  • CX8903:电动车手机充电器降压芯片,搭配协议实现快充
  • Linux入门学习:进程概念
  • k8s前置准备:配置虚拟机网络
  • 计算机网络 --- 初识协议
  • 多人在线聊天服务器