哈希表的应用(位图、布隆过滤器、海量数据处理)

目录

1. 位图

1.1 库中的位图

1.2 模拟实现位图

2. 布隆过滤器

2.1 布隆过滤器的模拟实现

3. 位图及布隆过滤器的应用(海量数据处理)

3.1 位图应用的例题

3.1.1 题目1

3.1.2 题目2

3.2 哈希切分+布隆过滤器的例题

3.2.1 题目3

3.2.2 题目4


1. 位图

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。适用于海量数据的状态:比如:40亿数据,需要16G内存;若用位图存放这些数据在不在的状态,只需要16/32G,约500M。

主要应用:

  1. 1. 快速查找某个数据是否在一个集合中
  2. 2. 排序 + 去重
  3. 3. 求两个集合的交集、并集等
  4. 4. 操作系统次磁盘中的标记等
  • 优点:节省空间、快
  • 缺点:只能处理整型数据
     

1.1 库中的位图

主要接口:set(将某位设为1) reset(将某位设为0) test(判断某一位是否为1)

	bitset<100> bs;
	//将某位设置为1
	bs.set(11);bs.set(5);bs.set(78);bs.set(23);bs.set(12);
	//将某位设置为0
	bs.reset(11);

	//判断某位是否为1
	for (size_t i = 0; i < 100; i++)
	{
		//cout << i << ":" << bs.test(i) << " ";
		//if (i != 0 && i % 10 == 0)
		//	cout << endl;
		if (bs.test(i) == 1)
			cout << i << " ";
	}
	cout << endl;

1.2 模拟实现位图

主要是三个接口的实现:set、reset、test

	template<size_t N>
	class BitSet
	{
	public:
		BitSet()
		{
			_bits.resize(N / 32 + 1, 0);//默认构造,会对位图进行初始化
		}

		// 把x映射的位标记成1
		void Set(size_t x)
		{
			assert(x < N);

			// 算出x映射的位在第i个整数
			// 算出x映射的位在这个整数的第j个位
			size_t i = x / 32;
			size_t j = x % 32;

			// _bits[i] 的第j位标记成1,并且不影响他的其他位
			_bits[i] |= (1 << j); //或等于
			//(1 << j)
			//00000001000000000
		}

		void Reset(size_t x)
		{
			assert(x < N);

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

			// _bits[i] 的第j位标记成0,并且不影响他的其他位
			_bits[i] &= (~(1 << j)); //与等于
			//对 1 << j 取反就行
			//~(1 << j)
			//1111111101111111111	
		}


		bool Test(size_t x)
		{
			assert(x < N);

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

			// 如果第j位是1,结果是非0,非0就是真
			// 如果第j为是0,结果是0,0就是假
			return _bits[i] & (1 << j);//直接把这一位取出来是1还是0
			//return (_bits[i] >> j) & 1;//这样写也可以
		}
	private:
		vector<int> _bits;
	};

2. 布隆过滤器

位图的本质:直接定址法哈希,每个整数映射一个比特位,但是只使用于整数。
布隆过滤器是位图的变形和延伸,适用于其他数据类型。(不提供删除操作,因为会影响其他元素)

应用场景:

判断某个昵称是否被使用过(把使用过的string放在布隆过滤器中,新来的string判断一下是否已存在)若利用位图的思想,直接将字符串利用hash函数转成int存入位图中,会引发大量的数据冲突(误判),即不同的string会转成同一个int,这种数据冲突是不可避免的、但是可以通过一定的方法降低这个冲突:每一个string映射多个bit位。

即便如此,判断存在(多个bit为1),还是可能误判;
                  判断不存在(有一个bit为0),是准确的;

                  随着布隆过滤器长度的增大,误判率会降低。

主要应用:

  • 网页URL去重;
  • 邮件过滤,使用布隆过滤器来做邮件的黑名单处理;
  • 对爬虫网址进行过滤,爬过的不用再爬;
  • 解决推荐过的内容不再推荐(短视屏往下滑动不会刷到重复)
  • 数据库内置布隆过滤器,如果数据不存在,就减少了数据库的IO请求,因为一旦一个值必定不存在的话,我们可以不用进行后续昂贵的查询请求。

优缺点:

  • 优点:效率高O(K), K为哈希函数的个数;节省空间(相对于平衡搜索树和哈希表)
  • 缺点:有误判;一般不支持删除操作

关于如何如何选择哈希函数个数和布隆过滤器的长度:

参考这两个公式(来源:网络博客)

其中:k为哈希函数的个数,m为布隆过滤器的长度,n为插入的元素的个数,p为误报率。

2.1 布隆过滤器的模拟实现

//布隆过滤器实际上是对位图的改进,所以实现上也是对位图的封装,一般只提供set和test接口,不能实现reset(删除)
template<size_t N, class K = std::string,class Hash1 = HashBKDR,class Hash2 = HashAP,class Hash3 = HashDJB>
//后面几个是字符串哈希函数的仿函数
class BloomFilter
{
public:
	void Set(const K& key)
	{
		//Hash1 hf1;
		//size_t i1 = hf1(key);//以下写法也可以
		size_t i1 = Hash1()(key) % N;//Hash1()是仿函数的匿名对象
		size_t i2 = Hash2()(key) % N;
		size_t i3 = Hash3()(key) % N;

		cout << i1 << " " << i2 << " " << i3 << endl;

		_bitset.Set(i1);
		_bitset.Set(i2);
		_bitset.Set(i3);
	}

	bool Test(const K& key)//判断是否存在
	{
		size_t i1 = Hash1()(key) % N;
		if (_bitset.Test(i1) == false)
		{
			return false;
		}

		size_t i2 = Hash2()(key) % N;
		if (_bitset.Test(i2) == false)
		{
			return false;
		}

		size_t i3 = Hash3()(key) % N;
		if (_bitset.Test(i3) == false)
		{
			return false;
		}

		// 这里3个位都在,有可能是其他key占了,在是不准确的,存在误判
		// 不在是准确的
		return true;
	}

private:
	bit::BitSet<N> _bitset; // 对位图的封装
	//bit::vector<char> _bitset;
};
	BloomFilter<100> bf;
	//布隆过滤器的长度一般取要插入元素个数的4倍以上,误判率就比较低,具体参考相关博客
	
	bf.Set("张三");  bf.Set("李四");  bf.Set("牛魔王");  bf.Set("红孩儿");
	cout << bf.Test("张三") << endl;
	cout << bf.Test("李四") << endl;
	cout << bf.Test("牛魔王") << endl;
	cout << bf.Test("红孩儿") << endl;
	cout << bf.Test("孙悟空") << endl;

3. 位图及布隆过滤器的应用(海量数据处理)

3.1 位图应用的例题

3.1.1 题目1

给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?

思路:100亿的整数范围还是42亿,因此用一个位图来存储只需要512M

将一个文件映射到位图中,再依次读取另一个文件的数据,看在不在位图中,在就是交集;

或者构建两个位图,求他们的交集;

3.1.2 题目2

给定100亿个整数,设计算法找到只出现一次的整数?

思路:用位图的思想,一个bit位能表示两种状态,这里至少是3种状态,因此需要两个bit位

00表示没出现;01表示只出现一次;10表示出现过2次及以上;

将所有数插入位图中,然后遍历位图,找出标志位01的位即为所求

3.2 哈希切分+布隆过滤器的例题

哈希切分的原理:就是将一个大文件,利用哈希的原理(i = Hash()(ip) % 100, i表示小文件的编号),将其分为若干个小文件。

哈希切割的特点:相同的ip一定进入了同一个小文件当中。

3.2.1 题目3

给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法

(1)近似算法:利用布隆过滤器

100亿个query(ip),可以看做string,假设为100G,那么两个文件一共是200G。

将A文件依次映射到一个布隆过滤器中,再依次读取B文件中的数据,与布隆过滤器里的内容比较,在就是交集,但是会有一定的误判率。

(2)精确算法:利用哈希切分 + 布隆过滤器

可以将AB文件都切割成200个小文件(哈希切分并不是均匀的,依次要保证小文件小于内存大小),按照同样的映射函数 i = Hash()(ip) % 200

这样AB中相同的ip,都进了各自对应的编号i的小文件,因此只需要依次比较Ai和Bi中的交集即可

 

3.2.2 题目4

给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现?

思路:哈希切分,切分成100个小文件(相同的ip一定进入了同一个小文件)

然后只需统计各个小文件各个ip的频次(比如用一个map<string, int>统计),找出每个小文件频次最高的ip地址进行比较即可;

要求 top K的ip,可以建一个K个元素小堆,后面的元素依次与堆顶元素比较,比它大就替换进堆,最终这个小堆就是top K ;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值