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

位图

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

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

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

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

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

位图解决
数据是否在给定的整型数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。
哈希的应用:位图和布隆过滤器_第1张图片
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

位图的实现

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 
#include "bitset.hpp"
#include 
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. 一般情况下不能从布隆过滤器中删除元素

你可能感兴趣的:(C++,C++,数据结构)