哈希思想的应用

目录

1.位图

位图的实现

题目变形一

题目变形二

题目变形三

总结:

2.布隆过滤器

概念

布隆过滤器的实现

3.哈希切割的思想


1.位图

     哈希表和位图是数据结构中常用的两种技术。哈希表是一种数据结构,通过哈希函数把数据和位置进行映射,来实现快速的寻找、插入和删除操作。哈希表适用于海量数据处理,索引,数据压缩等方面有广泛应用123. 位图主要用于海量数据处理,索引,数据压缩等方面有广泛应用。位图的应用场景包括:

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

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

3. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数。

如下题目:

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在
这40亿个数中。【腾讯】
位图解决
那么如何实现呢:
数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一
个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0
代表不存在。
  
    首先40亿个整数大概需要4G*4的空间,16个g的空间,我们是无法开出这么大的空间,其次我们用数组访问,因此还是连续l6个g的空间,很明显,我们无法实现,那么如何来解决这个问题呢?
      首先我们没必要把这么多数据存下来,我们只需要判断它在不在,而标记一个值在不在,最小的单位就是一个比特位,那么我们一个值映射一个比特位,就可以来表示这么多数据的存在情况。
此时我们只需要0.5G的空间即可,即42亿个比特位,此时0.5G就可以开出来了。

位图的实现

0.5G我们开不出bit类型的,我们只能开出整形或字符类型的。因此这里直接用vector来映射,因此我们需要计算出映射的位置。

如判断x是否存在,则我们需要计算x在第几个整形的第几个bit位上。

哈希思想的应用_第1张图片

templateclass bitset
{
public:
	bitset()
	{
		_bit.resize(N/32);
	}
	void set(size_t x)
	{
		size_t i = x / 32;//第几个整形
		size_t j = x % 32;//第几个位
		//找到该位置并置1 ,利用位运算计算
		//注意左移是向高位移,右移是向低位移
		_bit[i] |= (1 << j);//这里1左移再或上原数据,重新赋值后,该比特位就修改成1
	}
	void reset(size_t x)
	{
		//与set相反,这里是将原来的x有存在变为不存在,不存在变为存在

		size_t i = x / 32;//第几个整形
		size_t j = x % 32;//第几个位
		//找到该位置
		//1变0   0变1
		_bit[i] &= ~(1 << j);
	}

	//检测是否中存在,即该位置是1还是0
	bool test(size_t x)
	{
		size_t i = x / 32;
		size_t j = x % 32;
		return _bit[i] & (1 << j);
	}
private:
	//数据个数足够大,用16进制表示
	std::vector _bit;
	size_t size;
};

题目变形一

给100亿个整数,找出其中出现一次的数。
/用两个数组表示两个bit位   
//用两位bit位映射一个数三种状态,存在,不存在,个数
//00 不存在  01存在   10两个以上的个数
templateclass twobitset
{
public:
	void set(const size_t x)
	{
		if (bit1.test(x) == false && bit2.test(x) == false)//0 0 -> 0 1
		{
			bit2.set(x);
		}
		else if (bit1.test(x) == false && bit2.test(x) == true) //0 1 -> 1 0 
		{
			bit1.set(x);
			bit2.reset(x);
		}
	}
private:
	bitset bit1;
	bitset bit2;
};

题目变形二

给两个文件分别有100亿个整形数据,只有1G空间,找i出他们的交集。
解决方法:
各自映射到一个位图,两个位图如果都是存在,那么就是两者的交集的元素。

题目变形三

给定100亿个整数,1G内存,设计找到不查过2次的数据。
解决方案,用两个比特位表示四种状态:
00 不存在, 01存在,10有两个,11有三个以上。
templateclass twobitset
{
public:
	void set(const size_t x)
	{
		if (bit1.test(x) == false && bit2.test(x) == false)//0 0 -> 0 1
		{
			bit2.set(x);
		}
		else if (bit1.test(x) == false && bit2.test(x) == true) //0 1 -> 1 0 
		{
			bit1.set(x);
			bit2.reset(x);
		}
		else if (bit1.test(x) == true && bit2.test(x) == false;)// 10 ->11
		{
			bit1.set(x);
			bit2.set(x);
		}
	}
private:
	bitset bit1;
	bitset bit2;
};

总结:

对于搜索:

1.暴力查找,数据量太大,效率太低。

2.排序+二分查找 ,极大的提升了效率。但还是存在不足:要排序。数组不方便增删。

3.引入搜索树,AVL树,红黑树。查找效率已经很可观了,但空间消耗较高。

4.哈希  ,与搜索树效率差不多,但对于大量搜索的整型数据,通过映射bit的思想实现更加高效,且节省空间。(但只能处理整形)

对于其他类型的,引出了布隆过滤器:

2.布隆过滤器

概念

布隆过滤器是一种概率型数据结构,用于判断一个元素是否在一个集合中。它可以节省空间和时间,但有一定的误判率:

1. 布隆过滤器的核心是一个位数组,即一个只包含0和1的数组,以及一组哈希函数,即一组可以将任意元素映射到位数组的索引的函数。

2. 当要添加一个元素时,先用哈希函数计算出它在位数组中的多个索引,然后将这些索引对应的值都设为1. 当要检查一个元素是否存在时,也先用哈希函数计算出它在位数组中的多个索引,然后检查这些索引对应的值是否都为1. 如果都为1,说明元素可能存在,如果有一个为0,说明元素一定不存在。

哈希思想的应用_第2张图片

上述说了位图,我们知道位图可以来查找大量整形数据,但是对于其他类型,如字符型,就无法去查找,此时我们就需要有一种方法将他搞成整形。

直接用ascll码肯定不行,再通过哈希字符串算法转化一下,但是仍会有较大的冲突,还是有大概率的值是一样的,为了减少这些冲突,我们让一个字符串对应三个或者更多的哈希字符串算法转化后的整形,只有当这些整型值都存在的时候才存在,大大减少了重复带来的问题。各种字符串Hash函数 - clq - 博客园 (cnblogs.com)

但是还是存在问题:即使我们让一个字符串对应了三个转化后的数,但还是存在别的字符串也会对应这三个转化后的数(将不存在的判断为存在的),我们只能尽可能的减少这种情况,因此是否存在我们还是有不确定因素,但不存在我们一定能够能确定。

一个字符串对应的三个整数的对应比特位设置为1.

总的来说,存在误判率,但概率已经不是很大了。

那么如何选择n(插入元素个数)和k(映射的个数)的值呢?

有人给出了公式计算这里的m是布隆过滤器的长度。我们先以k=3先来实现。

哈希思想的应用_第3张图片

布隆过滤器的实现

#pragma once
#include"bitset.h"
//这里我们就选用3个映射值来对应一个字符串,每一个映射值我们选取一种hash字符串算法
#include
struct BKDR
{
	size_t operator()(const std::string& key)
	{
		// BKDR
		size_t hash = 0;
		for (auto e : key)
		{
			hash *= 31;
			hash += e;
		}

		return hash;
	}
};
struct AP
{
	size_t operator()(const std::string& str)
	{
		 size_t hash = 0;
		int i = 0;
		char ch = str[i];
		for (auto ch: str)
		{
		
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
			}
			i++;
		}
		return hash;
	}
};
struct SDB
{
	size_t operator()(const std::string& str)
	{
	     size_t hash = 0;
		for(auto ch:str)
		{
			hash = 65599 * hash + ch;
		}
		return hash;
	}
};
template
class BloomFilter
{
public:
	 //这里的类型就用泛型了
	void set(const K& key)
	{
		//三个值来映射
		size_t hash1 = HashFunc1()(key) % N;
		size_t hash2 = HashFunc2()(key) % N;
		size_t hash3 = HashFunc3()(key) % N;

		_bit.set(hash1);
		_bit.set(hash2);
		_bit.set(hash3);
	}
	bool test(const K& key)
	{
		size_t hash1 = HashFunc1()(key) % N;
		size_t hash2 = HashFunc2()(key) % N;
		size_t hash3 = HashFunc3()(key) % N;
		//只有当三个映射值都存在是才能表示存在
		if (_bit.test(hash1) == true&& _bit.test(hash2) == true&& _bit.test(hash3) == true)
		{
			return true;//存在误判
		}
		return false;
	}
	/*double wrongrate()
	{
		return  
	}*/

private:
	//用位图封装
	bitset _bit;
};

哈希思想的应用_第4张图片

 事实上,空间开得越多,选用的映射值越多,都会之误判律极大地降低。

   对于布隆过滤器,一般是不支持删除的,我们在删除一个值这会影响以他的字符串成员存在变成不存在,如若像删除,就必须要引用计数,增加计数位(多个比特位),即对应的哈希值在每一次被set之后,对应的计数位++,判断是否存在时,同时添加计数位是否为零,其次删除后,就减减计数位的值,不过还是消耗了空间。

3.哈希切割的思想

​​​​​​​     哈希切割是一种将大文件分割成若干个小文件的方法,以便于处理海量数据。该方法使用哈希函数将相同的数据分配到同一个文件中。这种方法可以用于处理日志文件等大小超过100GB的文件

哈希思想的应用_第5张图片

例题:

1. 给两个文件,分别有 100 亿个 query ,我们只有 1G 内存,如何找到两个文件交集?分别给出
精确算法和近似算法
解题思路:先将两个大文件各分割成新一个个小文件,之后两边的小文件分别比较,如何比较呢?
我们需要将100亿个query进行哈希切割,即把每一个query转化为整形在对其size取模,算出的值就是他对应的小文件。
变形2:
2.若是找出它出现次数前topk个query?如何查找
      还是哈希切割思想,进行哈希切割,我们将字符串(query)利用哈希算法转化为整型,此时的值对应他的位置,而这里的位置就是对应的小文件,
小文件我们就可以直接用map或者unoderedmap来映射query的次数,之后再利用栈来记录次数最多的即可。

你可能感兴趣的:(哈希算法,算法,数据结构)