[C++] 哈希的应用:位图和布隆过滤器


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

像这类题基础的思路应该排序+查找,但是这里题目要求有40亿个不同的整数,仅存储数据就要占用16G的内存,内存消耗过大,所以这里常规方法肯定行不通。

采用位图解决:
数据是否在给定的整形数据集合中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。如图:

在这里插入图片描述

位图

所谓位图,就是用每一个比特位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

数据在位图中的存储位置应该怎么找:
我们可以将位图看作一个个连续的整数数组,每个数组有32位,每一位代表一个数字是否存在:
相当于用一个比特位来表示一个整数是否存在,内存占用减少了32倍,40亿个整数完整存放需要占用16G的内存空间,而位图仅占用512M左右的内存空间。

比如我们现在要在位图中表示15这个数据是否存在:

  1. 先用 15/32=0 求出15应该存储在位图的第0个整数数组;-----数组位置
  2. 再用 15%32=15 求出应该存储在第15个bit位;-----bit位置
  3. 将该位置 置1表示15存在。

所以位图应该开多大的空间与它数据的大小范围有关系,与数据的个数没有关系。

位图的特点

使用场景:存放海量不重复数据的简单信息,但不需要存放数据本身

优点:节省空间,查找效率高:O(1)

位图的实现

底层封装一个整数数组,数组中每个bit位用1或0表示一个整数是否存在,初始化应该传入数据的范围

封装三个接口:实现在位图中插入,删除以及查找数据。


class BitSet {
private:
	//整数数组
	vector<int> _bit;

public:
	//位图的大小和数据范围有关,与数据个数无关
	BitSet(size_t rang) :_bit(rang / 32 + 1)
	{}

	//存储信息

	//查找信息

	//删除信息
};

插入

使用位置计算方法(先除以32,再对32取模)找到整数对应的bit位置,将其置1

//存储信息
	void Set(size_t num)
	{
		//先计算位置: /32  %32
		int idx = num / 32;		//整数位置
		int bitIdx = num % 32;	//bit位置
		//把对应bit位置1,按位或操作
		_bit[idx] |= 1 << bitIdx;
	}

查找

先算位置,再看值

	//查找信息
	bool find(size_t num)
	{
		//先算位置
		int idx = num / 32;
		int bitIdx = num % 32;
		//再看值
		return _bit[idx] >> bitIdx & 1;
	

删除

计算该整数的bit位置,将值置为0

//删除信息
	void Eraser(size_t num)
	{
		//先算位置
		int idx = num / 32;
		int bitIdx = num % 32;
		//置为0
		_bit[idx] &= (~(1 << bitIdx));
	}

位图完整代码:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<vector>
using namespace std;

class BitSet {
private:
	//整数数组
	vector<int> _bit;

public:
	//位图的大小和数据范围有关,与数据个数无关
	BitSet(size_t rang) :_bit(rang / 32 + 1)
	{}

	//存储信息
	void Set(size_t num)
	{
		//先计算位置: /32  %32
		int idx = num / 32;		//整数位置
		int bitIdx = num % 32;	//bit位置
		//把对应bit位置1,按位或操作
		_bit[idx] |= 1 << bitIdx;
	}

	//查找信息
	bool find(size_t num)
	{
		//先算位置
		int idx = num / 32;
		int bitIdx = num % 32;
		//再看值
		return _bit[idx] >> bitIdx & 1;
	}

	//删除信息
	void Reset(size_t num)
	{
		//先算位置
		int idx = num / 32;
		int bitIdx = num % 32;
		//置为0: 将1左移后取反,按位与操作
		_bit[idx] &= (~(1 << bitIdx));
	}

};

void test()
{
	BitSet bit(512);
	bit.Set(1);
	bit.Set(512);
	bit.Set(2);
	bit.Set(64);
	bit.Set(15);

	cout << "1是否存在:" << bit.find(1) << endl;
	cout << "2是否存在:" << bit.find(2) << endl;
	cout << "3是否存在:" << bit.find(3) << endl;
	cout << "512是否存在:" << bit.find(512) << endl;
	cout << "------------" << endl;
	bit.Reset(512);
	cout << "512是否存在:" << bit.find(512) << endl;
}


int main()
{
	test();
	return 0;
}

运行结果:
在这里插入图片描述

布隆过滤器

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

例如:S1、S2通过三个哈希函数计算得到如下位置:
在这里插入图片描述

虽然S1对应的哈希位置是1,4,8;但是1,4,8位置存在并不能保证S1一定存在,因为这些哈希位置也可能由其他的一个或多个数据得到。

假如我们还有一个对象S3的三个哈希位置对应是 2,3,7
那么只要2,3,7中有一个位为0就可以表示S2不存在。

那么我们如何确定应该使用多少个哈希函数来构建布隆过滤器:
有一个公式:k=m/n* ln2
k:哈希函数个数;
m:需要的bit位个数
n:元素个数
将上面的式子进行转换,也可以先确定哈希函数个数,再确定需要的bit位个数。

布隆过滤器的特点

应用场景:存放各种数据的简单信息
概率型容器:可以判断数据是否一定不存在或可能存在
一般不能删除:可能会存在误删
时间复杂度:O(k) k是哈希函数的个数

实现

  1. 底层封装一个位图用来执行对bit位的操作
  2. 插入:用哈希函数计算出对应的k个位置,然后置1
  3. 查找:查找k个对应哈希位置的值,如果都位1,表示可能存在;只要有一个不为1就表示绝对不存在
  4. 注意:布隆过滤器不提供删除接口,因为有可能造成误删,有可能将其他数据对应的哈希位置清零

代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
using namespace std;

//位图
class BitSet {
private:
	//整数数组
	vector<int> _bit;

public:
	//位图的大小和数据范围有关,与数据个数无关
	BitSet(size_t rang) :_bit(rang / 32 + 1)
	{}

	//存储信息
	void Set(size_t num)
	{
		//先计算位置: /32  %32
		int idx = num / 32;		//整数位置
		int bitIdx = num % 32;	//bit位置
		//把对应bit位置1,按位或操作
		_bit[idx] |= 1 << bitIdx;
	}

	//查找信息
	bool find(size_t num)
	{
		//先算位置
		int idx = num / 32;
		int bitIdx = num % 32;
		//再看值
		return _bit[idx] >> bitIdx & 1;
	}

	//删除信息
	void Reset(size_t num)
	{
		//先算位置
		int idx = num / 32;
		int bitIdx = num % 32;
		//置为0: 将1左移后取反,按位与操作
		_bit[idx] &= (~(1 << bitIdx));
	}

};



template <class T,class Hash1, class Hash2, class Hash3>
class BloomFilter {
private:
	//底层封装位图
	BitSet _bit;
	size_t _bitCount;	//记录bit位的个数
public:
	BloomFilter(size_t num)
		:_bit(5 * num)
		,_bitCount(5 * num)
	{}

	//存储信息:使用多个bit位
	void set(const T& val)
	{
		Hash1 h1;
		Hash2 h2;
		Hash3 h3;
		int idx1 = h1(val) % _bitCount;
		int idx2 = h2(val) % _bitCount;
		int idx3 = h3(val) % _bitCount;
		//用位图中封装的接口将三个哈希位置的值置1
		_bit.Set(idx1);
		_bit.Set(idx2);
		_bit.Set(idx3);
	}
	 
	//查找
	bool find(const T&val)
	{
		Hash1 h1;
		Hash2 h2;
		Hash3 h3;
		int idx1 = h1(val) % _bitCount;
		int idx2 = h2(val) % _bitCount;
		int idx3 = h3(val) % _bitCount;
		//
		if (!_bit.find(idx1))
			return false;
		if (!_bit.find(idx2))
			return false;
		if (!_bit.find(idx3))
			return false;

		return true;//可能存在


	}


};

struct HashFun1 {
	size_t operator()(const string & str)
	{
		size_t hash = 0;
		for (const auto &ch : str)
		{
			hash = hash * 131 + ch;
		}
		return hash;
	}
};
struct HashFun2 {
	size_t operator()(const string & str)
	{
		size_t hash = 0;
		for (const auto &ch : str)
		{
			hash = hash * 1313131 + ch;
		}
		return hash;
	}
};
struct HashFun3 {
	size_t operator()(const string & str)
	{
		size_t hash = 0;
		for (const auto &ch : str)
		{
			hash = hash * 65599 + ch;
		}
		return hash;
	}
};

void test()
{
	BloomFilter<string, HashFun1, HashFun2, HashFun3> blm(10);
	string str1 ="1https://editor.csdn.net/md?not_checkout=1&articleId=117413980";
	string str2 ="2https://editor.csdn.net/md?not_checkout=1&articleId=117413980";
	string str3 ="3https://editor.csdn.net/md?not_checkout=1&articleId=117413980";

	blm.set(str1);
	blm.set(str2);
	blm.set(str3);
	string str4 ="4https://editor.csdn.net/md?not_checkout=1&articleId=117413980";

	cout<<"str4存在吗: " << blm.find(str4) << endl;

}

int main()
{
	test();
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值