位图+布隆过滤器详解

目录

位图概念

位图结构

常用接口函数 

set

reset 

test 

size

Count

位图完整代码实现 

布隆过滤器 

概念

结构

插入 

查找

删除

完整代码 

布隆过滤器优点

布隆过滤器缺陷

总结 


位图概念

位图就是用bit位来存放某种状态,适用于海量数据,且无重复数据,一般应用与判断数据在不在。 

例:数据是否存在给定的整形数据中,结果是在或不在,两种状态,因此我们可以使用一个bit位来代表是否存在,0代表不存在,1代表存在。 

位图结构

位图底层采用vector实现,这里我们存储char类型,8个bit位为一个单元。 

template
	class bit_set
	{
	public:
		bit_set()
		{
			_bits.resize(N / 8 + 1, 0);
		}
	private:
		vector _bits;
	};

常用接口函数 

set

 将该比特位置1,也就是代表该数据存在。 

void set(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			_bits[i] |= 1 << j;
		}

reset 

将该位置置0,也就是删除该元素。 

void reset(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			_bits[i] &= ~(1 << j);
		}

test 

 查看该数据是否存在。

bool test(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			return _bits[i] & (1 << j);
		}

 size

 返回该位图可容纳的bit位个数

size_t size()const
		{
			return N + 8;
		}

 Count

 返回该位图中状态为1bit位的个数

	size_t Count()const
		{
			size_t count = 0;
			for (auto e : _bits)
			{
				for (size_t i = 0; i < 8; ++i)
				{
					if (e & (1 << i))
					{
						count++;
					}
				}
			}
			return count;
		}

位图完整代码实现 

 

namespace cxq
{
	template
	class bit_set
	{
	public:
		bit_set()
		{
			_bits.resize(N / 8 + 1, 0);
		}
		void set(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			_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);
		}
		size_t Count()const
		{
			size_t count = 0;
			for (auto e : _bits)
			{
				for (size_t i = 0; i < 8; ++i)
				{
					if (e & (1 << i))
					{
						count++;
					}
				}
			}
			return count;
		}
		size_t size()const
		{
			return N + 8;
		}
	private:
		vector _bits;
	};
}

布隆过滤器 

概念

当我们看短视频时,平台会给我们推送新的视频,一些我们已经看过的视频将不会再次推送,这是如何实现的呢?

服务器记录用户观看的视频记录,通过查找在不在记录内,不在就推送,在就说明已经看过,不再推送。那么如何快速查找呢?

  • 哈希表:通过哈希表存储用户记录,缺点:空间消耗大。
  • 位图:位图存储用户记录。 缺点:一般只能处理整形,如果是字符串,就无法处理。
  • 布隆过滤器:哈希表与位图的结合体。

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

通过将一个值映射到多个bit位,会大大缩小误判率。

因此,我们可以使用多种不同的哈希函数,将一个值映射到多个bit位。 

下面介绍三种比较优秀的哈希函数。

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;
	}
};

结构

N表示存储的数据个数,N*X表示我们所开的空间大小,所开空间越大,出现冲突的概率越低,误判的概率也越低,空间是最主要影响误判率的。 

template
class BloomFilter
{

private:
	bitset _bits;
};

插入 

 将映射的bit位全部置1

void set(const K& key)
	{
		size_t len = X * N;
		size_t index1 = HashFunc1()(key) % len;
		size_t index2 = HashFunc2()(key) % len;
		size_t index3 = HashFunc3()(key) % len;
		_bits.set(index1);
		_bits.set(index2);
		_bits.set(index3);
	}

查找

查看映射的bit位是否都为1,如果有一个不为1就说明一定不存在。

全为1说明可能在,此时可以再到数据库内查找。 

 

bool test(const K& key)
	{
		size_t len = X * N;
		size_t index1 = HashFunc1()(key) % len;
		if (!_bits.test(index1))
		{
			return false;
		}
		size_t index2 = HashFunc2()(key) % len;
		if (!_bits.test(index2))
		{
			return false;
		}
		size_t index3 = HashFunc3()(key) % len;
		if (!_bits.test(index3))
		{
			return false;
		}
		return true;
	}

删除

布隆过滤器一般不支持删除,因为删除一个值过后,可能影响映射到与该值相同bit位的值。

如果一定要实现删除,可以采用引用计数思想,我们可以采用多个bit位标记一个值,来代表这个值出现的次数,但是,这样就大大增加了空间消耗,失去了布隆过滤器的空间优势! 

完整代码 

//布隆过滤器的思想是一个值映射多个位置,这样误判的概率就会缩小
//主要影响误判率的是所开的空间大小
template
class BloomFilter
{
public:
	void set(const K& key)
	{
		size_t len = X * N;
		size_t index1 = HashFunc1()(key) % len;
		size_t index2 = HashFunc2()(key) % len;
		size_t index3 = HashFunc3()(key) % len;
		_bits.set(index1);
		_bits.set(index2);
		_bits.set(index3);
	}
	bool test(const K& key)
	{
		size_t len = X * N;
		size_t index1 = HashFunc1()(key) % len;
		if (!_bits.test(index1))
		{
			return false;
		}
		size_t index2 = HashFunc2()(key) % len;
		if (!_bits.test(index2))
		{
			return false;
		}
		size_t index3 = HashFunc3()(key) % len;
		if (!_bits.test(index3))
		{
			return false;
		}
		return true;
	}
	// 不支持删除,删除可能会影响其他值。
	void Reset(const K& key);
	//如果非要支持删除,我们可以采用引用计数
	//使用多个bit位来标记key所出现的个数
	//但是这样会增加空间,丧失布隆过滤器的优势
private:
	bitset _bits;
};

 

布隆过滤器优点

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

布隆过滤器缺陷

  • 1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
  • 2. 不能获取元素本身
  • 3. 一般情况下不能从布隆过滤器中删除元素
  • 4. 如果采用计数方式删除,可能会存在计数回绕问题

总结 

位图:节省空间,效率高。缺点:只能处理整数。

布隆过滤器:适用于数据量大,空间小,允许误判场景,可以处理字符串和自定义类型对象。

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