哈希的应用:位图和布隆过滤器

位图

首先我们来看一道腾讯的面试题:
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。
那么你会想到哪些解决方法呢?

  1. 遍历,时间复杂度 O(N)
  2. 排序 O(N * logN),利用二分查找 O(logN)

上面两种方法真的可以解决吗?40亿个不重复的无符号整数在内存中占多大空间呢?
232大概是42亿,4G空间大小,一个整数占4个字节,那就是16G的空间大小,实际上我们电脑的内存没有这么大。

那是否可以采用哈希进行映射处理呢?
可以开232个空间,对所有数直接定址法建立映射关系, 这样依然是16G的空间,内存不够用。

因此我们需要改进,可以开232个比特位,每一个比特位来标记一个数字,这样就只用了500M的空间大小。

位图解决
数据是否在给定的整型数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。
在这里插入图片描述
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

位图的实现

namespace BitSet
{
	class bitset
	{
	public:
		bitset(size_t N)
		{
			_bs.resize(N / 32 + 1, 0);
			_num = 0;
		}

		//将某一位设置成1
		void set(size_t x)
		{
			size_t index = x / 32; // 算出映射的位置在第几个整型
			size_t pos = x % 32;   // 算出x在整型的第几个位

			_bs[index] |= (1 << pos); // 第pos个位置成1
		}

		//将某一位设置成0
		void reset(size_t x)
		{
			size_t index = x / 32; // 算出映射的位置在第几个整型
			size_t pos = x % 32;   // 算出x在整型的第几个位

			_bs[index] &= ~(1 << pos); // 第pos个位置成0
		}

		// 判断x在不在(也就是说x映射的位是否为1)
		bool test(size_t x)
		{
			size_t index = x / 32; // 算出映射的位置在第几个整型
			size_t pos = x % 32;   // 算出x在整型的第几个位

			return _bs[index] & (1 << pos);
		}
	private:
		std::vector<int> _bs;
		size_t _num;
	};

	void test_bitset()
	{
		bitset bs(100);
		bs.set(99);
		bs.set(98);
		bs.set(97);
		bs.set(5);
		bs.reset(98);

		for (size_t i = 0; i < 100; ++i)
		{
			printf("[%d]:%d\n", i, bs.test(i));
		}
	}
}

布隆过滤器

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。 如何快速查找呢?

  1. 用哈希表存储用户记录,缺点:浪费空间
  2. 用位图存储用户记录,缺点:只能处理整型,对于字符串不能处理
  3. 将哈希与位图结合,即布隆过滤器

我们呢可以采用字符串哈希算法,先将字符串转换成整型,再使用位图进行处理。但是这样仍然不能解决数据冲突的问题,不同的字符串转换出来的整型可能会相同。因此只能尽可能的想办法减少冲突的概率,可以多开几个位图空间,每一条数据分别用不同的字符串哈希算法转换得到不同的整型数字,再分别取给每个位图中标记处理,这样很好的减少了数据冲突的概率。
在这里插入图片描述
布隆过滤器的实现:

#include <iostream>
#include "bitset.hpp"
#include <string>
using namespace std;

namespace BloomFilter
{
	struct HashStr1
	{
		// BKDR
		size_t operator()(const std::string& str)
		{
			size_t hash = 0;
			for (size_t i = 0; i < str.size(); ++i)
			{
				hash *= 131;
				hash += str[i];
			}
			return hash;
		}
	};

	struct HashStr2
	{
		// RSHash
		size_t operator()(const std::string& str)
		{
			size_t hash = 0;
			size_t magic = 63689; // 魔数
			for (size_t i = 0; i < str.size(); ++i)
			{
				hash *= magic;
				hash += str[i];
				magic *= 378551;
			}
			return hash;
		}
	};

	struct HashStr3
	{
		// SDBMHash
		size_t operator()(const std::string& str)
		{
			size_t hash = 0;
			for (size_t i = 0; i < str.size(); ++i)
			{
				hash *= 65599;
				hash += str[i];
			}
			return hash;
		}
	};

	template <class K = std::string, class Hash1 = HashStr1, class Hash2 = HashStr2, class Hash3 = HashStr3>
	class bloomfilter
	{
	public:
		//查阅资料知需要开数据量的4~5倍左右空间
		bloomfilter(size_t num)
			: _bs(5 * num)
			, _N(5 * num)
		{}

		void set(const K& key)
		{
			size_t index1 = Hash1()(key) % _N;
			size_t index2 = Hash2()(key) % _N;
			size_t index3 = Hash3()(key) % _N;

			//cout << index1 << endl;
			//cout << index2 << endl;
			//cout << index3 << endl << endl;

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

		bool test(const K& key)
		{
			size_t index1 = Hash1()(key) % _N;
			if (_bs.test(index1) == false)
			{
				return false;
			}
				
			size_t index2 = Hash2()(key) % _N;
			if (_bs.test(index2) == false)
			{
				return false;
			}
				
			size_t index3 = Hash3()(key) % _N;
			if (_bs.test(index3) == false)
			{
				return false;
			}
			return true; // 但是这里也不一定是真的在,还是可能存在误判
			// 判断在,是不准确的,可能存在误判
			// 判断不在,是准确
		}

		void reset(const K& key)
		{
			// 将映射的位置给置0就可以?
			// 不支持删除,可能会存在误删。一般布隆过滤器不支持删除
		}

	private:
		BitSet::bitset _bs; // 位图
		size_t _N;
	};

	void test_bloomfilter()
	{
		bloomfilter<std::string> bf(100);
		bf.set("abcd");
		bf.set("aadd");
		bf.set("bcad");

		cout << bf.test("abcd") << endl;
		cout << bf.test("aadd") << endl;
		cout << bf.test("bcad") << endl;
		cout << bf.test("cbad") << endl;
	}
}

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。

布隆过滤器优点:

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

布隆过滤器缺陷:

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
  2. 不能获取元素本身
  3. 一般情况下不能从布隆过滤器中删除元素
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
位图(Bitmap)和布隆过滤器(Bloom Filter)都是常用的数据结构,用于处理大规模数据集合,但它们有着不同的应用场景和用途。 位图是一种压缩数据结构,用于快速地判断某个元素是否在集合中。位图的实现方式是将每个元素映射到一个二进制位上,如果该元素存在于集合中,则将对应的二进制位标记为1,否则标记为0。这样,当需要查询某个元素是否在集合中时,只需要查找对应的二进制位即可。由于位图的实现方式非常简单,因此可以快速地进行插入和查询操作,而且占用的空间也非常小,适合处理大规模数据集合。 布隆过滤器也是一种快速判断元素是否存在于集合中的数据结构,但其实现方式与位图略有不同。布隆过滤器使用一组哈希函数将元素映射到多个二进制位上,并将对应的二进制位标记为1。当查询某个元素是否在集合中时,将该元素进行哈希映射,并查找对应的二进制位,如果所有的二进制位都被标记为1,则说明该元素可能存在于集合中,否则可以确定该元素不存在于集合中。布隆过滤器的优点是可以快速地判断一个元素不存在于集合中,而且占用的空间也比较小,但存在误判率的问题。 因此,位图布隆过滤器虽然都可以用来处理大规模数据集合,但它们的实现方式和应用场景有所不同。位图适用于需要快速地判断某个元素是否在集合中的场景,而布隆过滤器适用于需要快速地判断一个元素不存在于集合中的场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值