位图以及布隆过滤器

本文主要讲解哈希思想的实际应用,位图和布隆过滤器。

位图

讲解位图之前我们先来解答这样一道腾讯的面试题

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

很多人立马就想到了用哈希表或者红黑树,因为足够快,特别是哈希表,它的查询速度到达了O(1),想法是非常好的,但是我们可以仔细思考一下,这样真的可行吗?
答案是不现实,因为这是四十亿个整数,如果要全部写入到内存中,需要16GB大小的空间,并不是所有的计算机都能装下这些数据,换而言之,就算能完全装下,用这么大的空间去解决这个问题,也是不太好的,面试官也不会满意。

所以这个时候就引入了位图,我们可以想一想,这个问题只是让我们求解一个整数在或者不在,这两种状态,我们只需要用一个bit位就可以标记,例如1代表在,0代表不在。再配合哈希的映射思想,就产生了位图这个数据结构。如果不了解哈希,可以看看我之前的文章,我进行了很详细的讲解,我把链接贴在这里:哈希。

可以先看一下位图的结构和代码是怎么实现的
位图以及布隆过滤器_第1张图片

template<size_t N>
	class bitset {
	private:
		vector<char> bits;
	public:
		bitset()
		{
			bits.resize(N / 8,0);
		}
		//设置对应bit位
		void set(size_t x)
		{

			size_t i = x / 8;
			size_t j = x % 8;

			bits[i] |= (1 << j);
		}
		//重置对应数值bit位
		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);
		}
	};

其实再C++库中是有位图结构的:bitset

布隆过滤器

上面的位图结构能很便捷的记录整数在或者不在,那么布隆过滤器是什么呢?

布隆过滤器(Bloom Filter)是一种空间效率非常高的随机数据结构,用于判断一个元素是否可能存在于一个集合中。布隆过滤器可以在时间和空间上做到很高效,但是会有一定的误判率。
布隆过滤器的核心是一个位数组和一组哈希函数。假设位数组的长度是 m,有 k 个哈希函数,则每个元素经过 k 次哈希函数得到 k 个哈希值,将这 k 个值对 m 取模,得到 k 个位置,将这 k 个位置的值都设置为 1。当要查询一个元素时,同样地,将该元素经过 k 次哈希函数得到 k 个哈希值,查询这 k 个位置的值是否都为 1,如果都为 1,则说明该元素可能存在于集合中,如果有任何一个位置的值为 0,则说明该元素一定不存在于集合中。
布隆过滤器的优点在于,它可以很高效地判断一个元素是否存在于一个集合中,而且空间效率非常高,因为它只需要使用一个位数组和一组哈希函数即可。但它的缺点在于,存在一定的误判率,也就是说,有可能某个元素不在集合中,但是经过判断后,布隆过滤器认为它存在于集合中。

简单来说就是一个元素通过K个哈希函数映射K个bit位,布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。
位图以及布隆过滤器_第2张图片

布隆过滤器实现代码:

struct BKDRHash
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (auto ch : s)
			{
				hash += ch;
				hash *= 31;
			}

			return hash;
		}
	};
	struct APHash
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (long i = 0; i < s.size(); i++)
			{
				size_t ch = s[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& s)
		{
			size_t hash = 5381;
			for (auto ch : s)
			{
				hash += (hash << 5) + ch;
			}
			return hash;
		}
	};
	template<size_t N,
	class K = string,
	class Hash1 = BKDRHash,
	class Hash2 = APHash,
	class Hash3 = DJBHash>
	class BloomFilter {

	private:
			static const size_t _X = 6;
			bitmap<N* _X> bts;
			size_t len = N * _X;
	public:

		void set(const K& key)
		{
			size_t hash1 = BKDRHash()(key) % len;
			bts.set(hash1);
			
			size_t hash2 = APHash()(key) % len;
			bts.set(hash2);

			size_t hash3 = DJBHash()(key) % len;
			bts.set(hash3);
		}
		bool test(const K& key)
		{
			if (!bts.test(BKDRHash()(key) % len))
			{
				return false;
			}
			if (!bts.test(APHash()(key) % len))
			{
				return false;
			}
			if (!bts.test(DJBHash()(key) % len))
			{
				return false;
			}
			return true;
		}
	};

布隆过滤器使用的哈希函数越多误判率越低,但是占用的空间也就越大。用三个哈希函数是比较合适的。

这就是位图和布隆过滤器的原理和实现,如果对您有所帮助,点个赞和关注吧!

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