哈希的应用 位图+布隆过滤器+海量数据处理

位图

1.什么是位图

先看一道面试题:
给60亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这60亿个数中
1.如果用set/unordered_set 插入然后调用函数count的话内存不够,60亿个整数大概需要24g的内存
2.如果用外排序+二分查找也不可以,二分查找需要所有的整数都是有序的,并且要是顺序存储结构支持随机访问
此时如果用哈希的直接定值法——用一个比特位表示标识映射值在不在,此时需要的内存大概为750M,这个对于内存来说也不是不可以解决,这样的解决方法就叫做位图
位图: 就是用每一位来存放某种状态,适用于海量数据,且数据无重复的场景。通常是用来判断某个数据是否存在的。

2.实现思路

在这里插入图片描述

3.位图的实现

注意:位图的实现尽量不要移动需要映射的值X,因为涉及到补位的问题,此外映射值改为X要好一点。

template <size_t N>
	class Bitset
	{
	public:
		Bitset()
		{
			_bits.resize(N / 8 + 1);//保证足够多的比特位,最多浪费8个

		}
		//X映射的比特位标记成1
		void set(size_t x)
		{
			//x映射的比特位在第几个char对象
			size_t i = x / 8;
			//x在char的第几个比特位
			size_t j = x % 8;
			_bits[i] |= (1 << j);
		}
		void reset(size_t x)
		{
			//x映射的比特位在第几个char对象
			size_t i = x / 8;
			size_t j = x % 8;
			_bits[i] &= (~(1 << j));//次数不能用异或,否则如果原本不存在x异或之后反而存在了
		}
	   bool  test(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			return _bits[i] & (1 << j);//此处有一个隐式类型转换,非0就是真,0就是假
		}
	private:
		vector<char>_bits;

	};

4.位图的应用

1.给定100亿个整数,设计算法找到只出现一次的整数?
在这里插入图片描述

template<class N>
		class two_bitset
		{
		public:
			void set(size_t x)
			{
				int exit1 = _bst1.test(x);
				int exit2 = _bst2.test(x);
				if (exit1 == 0 && exit2 == 0)
				{
					_bst2.set(x);
				}
				else
				{
					_bst1.set(x);
					_bst2.reset(x);
				}
			}
			bool is_once(size_t x)
			{
				return (!_bst1.test(x)) && _bst2.test(x);

			}
		private:
			Bitset<N>_bst1;
			Bitset<N>_bst2;
		};

2.位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
与上述类似,也是用两个比特位表示映射的状态
此时
00 出现0次
01 出现1次
10 出现两次
11 出现3次及以上,取中间两次即可
3.给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
思路:
每个文件都有100亿个整数,此时文件中一定有相同的数,因为整数的最大值才是42亿,此时我们的措施是将每个文件都放到一个位图中,每个文件最多42亿刚好500M,两个文件就是1g,因为放进位图中还起到了去重的作用,最后挨个找交集就可以了,如果此时位图1和位图2对应的比特位都是1的话,那么就是交集。

布隆过滤器

可以参考这个链接一个大牛写的布隆过滤器

1.布隆过滤器思路

用哈希表存储用户记录,缺点:浪费空间
上述的位图只能处理整形,但是如果内容编号是字符串,就无法处理了,此时将位图和哈希结合就是布隆过滤器。
首先将字符串用仿函数转变成整形,但是因为字符串中的字符个数是有限的,必定会存在两个不同的字符串用同一个仿函数映射成相同的整数,所以我们需要用多个不同的哈希函数生成多个哈希值,并对每个生成的bit位置
eg:现在有一个字符串"baidu"和三个哈希函数,分别生成了哈希值1 4 7
在这里插入图片描述
又有一个字符串“tencent”此时其哈希函数映射后对应3 4 8的值如下
在这里插入图片描述
可以看到上述有4号位两个字符串的哈希都返回这个bit位,结果可以看到,如果我们查找一个字符串时,如果该字符串对应的bit位为6,说明这个字符串不可能存在,但是如果我们查找一个字符串,此时其对应的bit位都是1,但是该字符串也不一定是存在的,因为有可能多重覆盖,现在有很多的字符串,一个字符串覆盖一个位置,导致本来这个字符串不存在,但事实就是该字符串映射的三个bit位都是1。
结论:
布隆过滤器在查找一个字符串时,若该字符串存在,也不能说明一定存在,但是如果该字符串不存在,那么一定是不存在的。
下面这张图是网上一些人的经验数据
在这里插入图片描述
可以看出,哈希函数个数与布隆过滤器的长度与错误率成反比,及哈希函数个数越多,布隆过滤器越长,误差率越低。
布隆过滤器支持删除吗?
传统的布隆过率器是不支持删除的,为什么呢?
删除的话是添加比特位计数
一个比特位标识一个位置
八个比特位:0–255
十六个比特位:0–65535

在这里插入图片描述可以看出,上述即使删除了,如果某个bit位上有多个值映射对应,还是会有可能出现误差,并且用布隆过滤器增加了空间的消耗,而布隆过滤器的本质就是为了节省空间,支持删除的话就不那么节省了,如果给每个bit位都增加计数的话,还不如增加布隆过滤器的长度,所以说不支持删除,用于那些允许有些许误判的场景。

2.布隆过滤器的实现

一个元素用多个哈希函数映射到一个位图,被映射到的那个bit位为1,然后多个哈希函数映射多个位图,查找的时候只要对应一个位图bit为0,那么说明该元素一定不在哈希表中,否则可能在哈希表中。

//布隆过滤器的实现
	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;
		}
	};


	struct JSHash
	{
		size_t operator()(const string& s)
		{
			size_t hash = 1315423911;
			for (auto ch : s)
			{
				hash ^= ((hash << 5) + ch + (hash >> 2));
			}
			return hash;
		}
	};
	template<size_t M,class K = string,//M位布隆过滤器的长度
		class HashFunc1 = BKDRHash,
		class HashFunc2 = APHash,
		class HashFunc3 = DJBHash,
		class HashFunc4 = JSHash>
	class BloomFilter
	{
	public:
		void set(const K& key)
		{
			size_t hash1 = HashFunc1()(key) % M;
			size_t hash2 = HashFunc2()(key) % M;
			size_t hash3 = HashFunc3()(key) % M;
			size_t hash4 = HashFunc4()(key) % M;
			_bs.set(hash1);
			_bs.set(hash2);
			_bs.set(hash3);
			_bs.set(hash4);

		}
		bool Test(const K& key)
		{
			size_t hash1 = HashFunc1()(key) % M;
			if (_bs.test(hash1)==false)
			{
				return false;
			}
			size_t hash2 = HashFunc2()(key) % M;
			if (_bs.test(hash2) == false)
			{
				return false;

			}
			size_t hash3 = HashFunc3()(key) % M;
			if (_bs.test(hash3) == false)
			{
				return false;
			}
			size_t hash4 = HashFunc4()(key) % M;
			if (_bs.test(hash4) == false)
			{
				return false;
			}
			return true;//存在误判
		}


	private:
		Bitset<M>_bs;
	};

3.布隆过滤器小结与应用

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

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
  2. 不能获取元素本身
  3. 一般情况下不能从布隆过滤器中删除元素
    应用
    eg:1.注册用户名时
    如果该用户名不存在,则可以直接使用,
    如果存在,再去数据库确认一遍
    2.检查是否为黑名单是
    如果不是,直接放行,
    如果是,再去系统数据库确认一遍。

海量数据处理(哈希切分)

1、给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?如何快速找到top K的IP?

在这里插入图片描述

  1. 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出
    精确算法和近似算法
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天少点debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值