哈希应用全解

目录

一.   位图

1.1   概念

1.2   bitset使用

1.3   实现

1.4   位图的应用

 二.   布隆过滤器

2.1   布隆过滤器的提出

2.2   概念

 2.3   布隆过滤器基本结构定义

2.4   插入

2.5   查找

2.6   删除

2.7   布隆过滤器优点

2.8   布隆过滤器缺点

 三.   哈希切割

四.   面试题


一.   位图

1.1   概念

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

例如:给你40亿个不重复的无符号整数,没有排过序,给你一个无符号整数,如何快速判断这个树在不在这40亿个数之中?

第一眼看是不是人都傻了,40亿个整数,相当于16G的内存,如果我们将他放到一个数组中,这个数组的长度就爆炸了,而且内存也开不了这么大的空间。那怎么办呢?

我们放在数组中是以一个整型的形式存储的,那我们是不是可以缩小一下用一个bite位来看呢,是不是这个内存占用就减小了8倍,是不是大大节约了空间。

这就是我们位图的基本思想,那好我们就用每一个bite位的状态(0或者1)来显示在不在。

1.2   bitset使用

根据上面的分析,可以看出我们位图有着三个基本的实现。

  1. 将一个bite位的状态变为1。
  2. 将一个bite位的状态变为0。
  3. 检查这个数所在的位置状态是不是1,为1就返回true。
set(size_t pos);//将pos数所映射的bit位标记为1

reset(size_t pos);//将pos数所映射的bit标记回0

test(size_t pos);//检测pos数所映射的bit为是否为1,为1则返回true

1.3   实现

我们这里用vector<int>来实现位图这个结构,为什么要用int呢?因为我们是用32位bite位为一个单位,刚好是一个整形,所以就用int,也可以用char。

那么如果有N个数,首先对vector进行开空间,我们这里要开N/32+1个整型空间,并把每个位置的初始值给0,为什么要多开一个呢?因为不能保证N一定是32的倍数,可能会多余几个数,所以多开32个bite位,即+1。而且即使浪费也只多浪费一个整型。

  1. set函数:将某个数字对应的位置的状态变为1。具体操作是:首先看这个数是位于第几个空间(一个空间32个bite位)-》x/32,再看这个数在这个空间的那个位置-》x%32,那么让这个位置的状态变为1,可以用到或(|)运算
  2. reset函数:将某个数字对应的位置的状态变为0。具体操作是:还是先找到位于哪个空间的哪个位置,然后用与(&)运算
  3. test函数:检测某个数的状态是否为1。具体操作是:找到这个数的位置,然后用这个位置的状态与运算1
//把x映射的位置标记成1
void set(size_t x)
{
	assert(x <= N);
	size_t i = x / 32;
	size_t j = x % 32;
	_bits[i] |= (1 << j);//此处左移是向高位移的意思,不是普通意义上想左移
	//此处是以一个整型为单位
}

//把x映射的位置标记成0
void reset(size_t x)
{
	assert(x <= N);
	size_t i = x / 32;
	size_t j = x % 32;
	_bits[i] &= ~(1 << j);
}

bool test(size_t x)
{
	assert(x <= N);
	size_t i = x / 32;
	size_t j = x % 32;

	return _bits[i] & (1 << j);
}

 需要注意的是:我们这里不论是&还是|,都是对于同一bite位上的数来运算。因此要用到移位操作符(<<),这里左移指的是向高位移动,并不是书面上的向左移。

对于set来说:

对于reset来说:

对于test来说:

1.4   位图的应用

  1. 快速查找某个数据是否在一个集合中
  2. 排序 + 去重
  3. 求两个集合的交集、并集等
  4. 操作系统中磁盘块标记

 二.   布隆过滤器

2.1   布隆过滤器的提出

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉 那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用 户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那 些已经存在的记录。 如何快速查找呢?

1. 用哈希表存储用户记录,缺点:浪费空间

2. 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理 了。

3. 将哈希与位图结合,即布隆过滤器

2.2   概念

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

布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位就为1。 

 2.3   布隆过滤器基本结构定义

布隆过滤器用到了位图的三个函数。

struct HashFuncBKDR
{
	// BKDR
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash *= 131;
			hash += ch;
		}

		return hash;
	}
};

struct HashFuncAP
{
	// AP
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (size_t 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 HashFuncDJB
{
	// DJB
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash = hash * 33 ^ ch;
		}

		return hash;
	}
};

template<size_t N,class K=string,
	class Hash1=HashFuncBKDR,
	class Hash2=HashFuncAP,
	class Hash3=HashFuncDJB>
class BloomFilter
{
private:
	static const size_t M = 5 * N;
	std::bitset<M>* _bs=new std::bitset<M>;
};

2.4   插入

void set(const K& key)
{
	size_t hash1 = Hash1()(key) % M;//匿名对象调用
	size_t hash2 = Hash2()(key) % M;
	size_t hash3 = Hash3()(key) % M;

	_bs->set(hash1);
	_bs->set(hash2);
	_bs->set(hash3);
}

用哈希函数算出对应映射的位置,将位置的状态变为1即可。

2.5   查找

bool Test(const K& key)
{
	size_t hash1= Hash1()(key) % M;
	if (_bs->test(hash1) == false)
	{
		return false;
	}

	size_t hash2 = Hash2()(key) % M;
	if (_bs->test(hash2) == false)
	{
		return false;
	}

	size_t hash3 = Hash3()(key) % M;
	if (_bs->test(hash3) == false)
	{
		return false;
	}
	return true;
}

分别计算每个哈希值对应的比特位置存储的是否为0,只要有一个为0,代表该元素一定不在哈希表中,否则可能在哈希表中。

注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可 能存在,因为有些哈希函数存在一定的误判。

2.6   删除

布隆过滤器是不能支持删除的,因为不同的元素可能映射相同的位置,删除了某个元素后,可能改变了其他元素在对应位置的状态。

但是有一种删除方法是:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k分哈希函数计算处的哈希地址)+1,在删除元素时,给k个计数器-1,这样通过多占几倍的存储空间代价来增加删除操作。

2.7   布隆过滤器优点

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

2.8   布隆过滤器缺点

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

 三.   哈希切割

有这么一道题:给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 与上题条件相同,如何找到top K的IP?

此处的话,可以用map<string,int>,属于kv模型,不能用布隆过滤器(布隆过滤器本质就是位图,是解决类型不是整型的时候的情况),我们可以用到哈希切割的思想。

我们可以依次取ip,让每个ip%100,这样就将ip分到100个不同的区间里面,那么相同的ip就在相同的区间了。

还有一个问题,可能出现某个ip太多,这样冲突很多,那么map就会抛异常,这时就需要更换哈希函数,进行二次切分处理。

而找topk的话,就可以建堆来处理。

四.   面试题

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

这个题还是用位图来解决。需要注意的是一种情况:如果我们位图需要1个G的内存,但是只有512MB怎么办呢?

我们可以分成两个位图来处理:第一个位图处理0~2^31-1的数据直接映射,第二个位图处理2^31~2^32-1的数据减去2^31之后再映射。

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

我们可以分别映射到两个位图中去,那么在两个位图相同位置状态都为1的元素就是交集。

(3)1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数。

我们还是可以用两个位图来解决,那么两个位图某个相同位置的状态组合起来就只有四种(00,01,10,11),我们用00代表0次,01代表1次,10代表两次,11代表3次及以上。那么只需看哪些是00,01,10的就可以看出了。

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

这里的近似算法就是我们的布隆过滤器,我们主要来讲讲精确算法。 

我们可以对两个文件的query进行哈希切割(query%1000),分别是两个文件中的query进入两组1000个的文件组中,第一个文件的放到A中,第二个文件放到B中。A和B的query继续分别放到连个该set中,找交集即可。

(5)如何扩展BloomFilter使得它支持删除元素的操作

每个位置给多个bite的引用计数做标记,比如一个位置给8bite位做标记,但是这样空间消耗就高了。


总结

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值