位图与布隆

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/baidu_37964044/article/details/81906963

1K=1024byte

1M=1024k=1024*1024byte(约100万个字节)

1G=1024M=1024*1024*1024byte(约为10亿个字节)

先看一个题,给40亿个不重复无符号整数,给一个无符号整数,快速判断一个数是否在这40亿个数中。

判断一个数在或者不在,我们通常有以下方法:

1.暴力查找  时间复杂度O(N)   

2.排序完,进行二分查找O(N)+O(N lgN)

3.哈希/搜索树  哈希O(1)搜索树O(NlgN)

上面的可以看出,如果只查找一次,用暴力查找比较好,查多次用后面的较好。

但是上面的

1.暴力查找需要报40亿个整数都要加入磁盘中16G,存不下

 2.排序也一样,存不下

 3.如果用搜索树当然更不行,一个节点有左右孩子,数等,比16G还大,哈希桶也存不下,有一个指针,和数据

   这时我们想到直接定址法的哈希,但是直接定地址法的哈希要开整形最大值那么大的整形的空间。42亿九千万个整形                       大概也就是16G多,存不下,但是我们想到只让判断这个数在或者不在两种状态,用一个比特位就可以表示,没有必要                   开一个整形,这样就不需要16G,只需16G/4/8=500M,这时我们的内存就可以存下,这就是所说的位图

位图:也是一种直接定址法的哈希。本质是用一个数组,但是用一个比特位来表示一种状态,0表示不存在,1表示存在

位图的具体实现如下:

#include<iostream>
#include<vector>
using namespace std;
class BitSet
{

public:
	BitSet(size_t range)
	{
		_bits.resize((range >> 5) + 1, 0);//注意移位运算符,优先级特别低
	}
	void Set(size_t x)
	{
		size_t num = x >> 5;//知道在哪个字节
		size_t pos = x % 32;//知道在哪个比特位
		//把这一位置为1,给这一位或1,给其他位或0.0与任何数或为任何数
		_bits[num] |= (1 << pos);
	}
	void ReSet(size_t x)
	{
		size_t num = x >> 5;
		size_t pos = x % 32;
		//1与任何数与为任何数,0与任何数与为0
		_bits[num] &= ~(1 << pos);
	}
	bool Test(size_t x)
	{
		size_t num = x >> 5;
		size_t pos = x % 32;
		//如何测试这一位为1,还是0
		return _bits[num] & (1 << pos);

	}
protected:
	vector<size_t >_bits;
};

int main()
{

	//BitSet bs(-1);
	BitSet bs(~0);
	bs.Set(1);
	bs.Set(19);
	bs.Set(1000);
	bs.ReSet(1000);
	cout<<bs.Test(1)<<endl;
	cout<<bs.Test(19)<<endl;
	cout<<bs.Test(1000)<<endl;
	system("pause");
	return 0;
}

在40亿个字符串集合中,快速判断一个字符串在或者不在?

这个题我们也可以用位图,可是这是字符串又不是整数,可是我们可以把字符串通过在字符串哈希转换为整数。可是这时候会产生哈希冲突,会产生误判,一个数明明没在,却判断为在,于是我们可以将一个元素经过不同的哈希函数,映射到多个位置,如果这几个位置上都为1,我们就认为这个元素存在,如果有一个位为0,就表示不存在。这就是所谓的布隆。

布隆:是位图+字符串哈希的结合,基本思想是,通过一个哈希函数将一个元素映射到一个位置,我们只要判断这个位置是不是存在,就能判断是否存在,但是由于哈希冲突的原因,不同的元素经过哈希函数会映射到相同的哈希地址,导致误判,为了缓解误判,我们将一个元素经过多个散列函数映射到多个位置上,如果这多个位都存在,我们认为存在,如果有一个位不存在,则不存在。

注意:布隆过滤器是存在不准确,不存在准确。

1.怎样降低误判的概率?

多个哈希函数进行映射

具体实现代码如下:

#include<iostream>
#include<vector>
using namespace std;
class BitSet
{

public:
	BitSet(size_t range)
	{
		_bits.resize((range >> 5) + 1, 0);//注意移位运算符,优先级特别低
	}
	void Set(size_t x)
	{
		size_t num = x >> 5;//知道在哪个字节
		size_t pos = x % 32;//知道在哪个比特位
		//把这一位置为1,给这一位或1,给其他位或0.0与任何数或为任何数
		_bits[num] |= (1 << pos);
	}
	void ReSet(size_t x)
	{
		size_t num = x >> 5;
		size_t pos = x % 32;
		//1与任何数与为任何数,0与任何数与为任何数
		_bits[num] &= ~(1 << pos);
	}
	bool Test(size_t x)
	{
		size_t num = x >> 5;
		size_t pos = x % 32;
		//如何测试这一位为1,还是0
		return _bits[num] & (1 << pos);

	}
protected:
	vector<size_t >_bits;
};
struct  _HashFunc1
{
	size_t operator()(const string& str)
	{
		size_t num = 0;
		for (int i = 0; i < str.size(); i++)
		{
			num = num * 131 + str[i];
		}
		return num;
	}
};
struct _HashFunc2
{
	size_t operator()(const string& str)
	{
		size_t num = 0;
		for (int i = 0; i < str.size(); i++)
		{
			num = num * 65596 + str[i];
		}
		return num;
	}
};
struct _HashFunc3
{

	size_t operator()(const string& str)
	{
		size_t magic = 63689;
		size_t num = 0;
		for (int i = 0; i < str.size(); i++)
		{
			
			num = num*magic + str[i];
			magic = magic * 378551;
		}
		return num;
	}
};
struct _HashFunc4
{
	size_t operator()(const string& str)
	{
		size_t num = 0;
		for (int i = 0; i < str.size(); i++)
		{
			if ((i & 1) == 0)
			{
				num ^= ((num << 7) ^ str[i] ^ (num >> 3));
			}
			else
			{
				num ^= (~((num << 1) ^ str[i] ^ (num >> 5)));
			}
		}
		return num;
	}

};
class BloomFilter
{
public:
	BloomFilter(size_t x)
		:_bs(x)
		, _capacity(x)
	{}
	void Set(const string & key)
	{
		size_t index1 = (_HashFunc1()(key)) % _capacity;
		size_t index2 = (_HashFunc2()(key)) % _capacity;
		size_t index3 = (_HashFunc3()(key)) % _capacity;
		size_t index4 = (_HashFunc4()(key)) % _capacity;
		_bs.Set(index1);
		_bs.Set(index2);
		_bs.Set(index3);
		_bs.Set(index4);

	}
	int Test(const string& key)
	{
		size_t index1 = (_HashFunc1()(key)) % _capacity;
		size_t index2 = (_HashFunc2()(key)) % _capacity;
		size_t index3 = (_HashFunc3()(key)) % _capacity;
		size_t index4 = (_HashFunc4()(key)) % _capacity;
		//只有四个位都为1才存在
		if (!_bs.Test(index1))
		{
			return false;
		}
		if (!_bs.Test(index2))
		{
			return false;
		}
		if (!_bs.Test(index2))
		{
			return false;
		}
		if (!_bs.Test(index2))
		{
			return false;
		}

		return true;
	}
private:
	BitSet _bs;
	size_t _capacity;

};
int main()
{
	BloomFilter bf(-1);
	bf.Set("nihao");
	bf.Set("wohenhao");
	bf.Set("nihaoma");
	bf.Set("nihao ");
	cout<<bf.Test("nihao")<<endl;
	cout<<bf.Test("wohenhao")<<endl;
	cout<<bf.Test("nihaoma")<<endl;
	cout<<bf.Test("nihao ")<<endl;
	cout << bf.Test("nihao         ") << endl;

	system("pause");
	return 0;
}

以上的代码没有对布隆进行删除操作?

2.如何对布隆过滤器进行删除操作

因为一个位可能对应着多个元素,所以当我们删除一个位的时候,不能直接删除,否则会影响其他元素。这时候我们采取引用计数的方式来实现删除操作,为了记录这个元素出现的次数,我们不能用一个位来表示状态了,我们可以用一个无符号整形来表示状态。这样就要用四个字节表示在或者不在。我们不支持这样做,因为我们用布隆就是为了解决避免空间的浪费,如果增加了,取消设置,违背了。

3.布隆过滤器的优点:空间效率和查询时间都很好,超过一般的算法。插入和查找0(1)

   布隆过滤器的缺点:误判

4.布隆过滤器的应用场景:

爬虫爬某个网站是否被访问过(允许漏爬)。

检测一个英语单词拼写是否拼写正确(也就是判断它在某个已知的字典里)

破案当中一个嫌疑犯的名字是否已经在嫌疑犯名单上

过滤垃圾邮件

位图布隆对比:位图针对整形,布隆针对字符串。

猜你喜欢

转载自blog.csdn.net/baidu_37964044/article/details/81906963