C++知识点 -- 哈希的应用

C++知识点 – 哈希的应用


一、位图

1.概念

如果给40亿个不重复的无符号整数,没排过序,给一个无符号整数,如何判断一个数是否在这40亿个数中?
如果我们使用搜索树或者哈希表来结局这个问题,那么会面临40亿个整数无法整个放进数据结构中,内存会放不下;
如果我们使用排序+二分查找的办法,大型数据一般是存放在磁盘上的,不好支持二分查找,效率低;
为此,我们引入位图的概念,位图就是用每一位来存放某种状态,一个数据是否在给定的整形数据中,结果是在与不在两种状态,就可以使用一个二进制位来表示数据是否存在,用1表示存在,0表示不存在;
在这里插入图片描述
位图适用于海量数据,数据无重复的场景,通常是用来判断一个数据是否存在的;

注:(大型数据存储的空间占用)
在这里插入图片描述
1G约等于10亿字节

2.位图开空间

位图开开空间是跟范围有关的,与数据个数无关,因为映射都是用整形值的大小去映射的,整型值最多只有42亿个,所以位图开空间最多42亿bit,也就是512M;
在这里插入图片描述
开空间时,以字节为单位,我们可以使用vector的结构来做位图的基础数据结构;

3.位图的特点

1.搜索快,节省空间;
2.相对局限,只能映射处理整形;
3.对位图遍历后,还可以实现怕排序+去重的效果;

4.位图的实现

namespace lmx
{
	template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			_bits.resize(N / 8 + 1);//开空间时多开一个字节,保证所有数据都能放得下
		}

		//对应位置1
		void set(size_t x)
		{
			size_t i = x / 8;//x在哪一个字节
			size_t j = x % 8;//x在该字节的第几位

			_bits[i] |= (1 << j);//将位图中x对应的位置1
		}

		//对应位置0
		void reset(size_t x)
		{
			size_t i = x / 8;//x在哪一个字节
			size_t j = x % 8;//x在该字节的第几位

			_bits[i] &= ~(1 << j);//将位图中x对应的位置0
		}

		//探测某位是0还是1
		bool test(size_t x)
		{
			size_t i = x / 8;//x在哪一个字节
			size_t j = x % 8;//x在该字节的第几位

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

	private:
		vector<char> _bits;
	};
}

5.位图的应用

1.给定100亿个整数,设计算法找到只出现一次的树数;
kv模型统计次数,可以建立一个两个位表示一个数据的位图结构,00表示出现0次,01出现1次,10表示出现2次及以上;

	template<size_t N>
	class twobitset
	{
	public:
		void set(size_t x)
		{
			bool inset1 = _bs1.test(x);
			bool inset2 = _bs2.test(x);

			// 00
			if (inset1 == false && inset2 == false)
			{
				// -> 01
				_bs2.set(x);
			}
			else if (inset1 == false && inset2 == true)
			{
				// ->10
				_bs1.set(x);
				_bs2.reset(x);
			}
			else if (inset1 == true && inset2 == false)
			{
				// ->11
				_bs1.set(x);
				_bs2.set(x);
			}
		}

		void print_once_num()
		{
			for (size_t i = 0; i < N; ++i)
			{
				if (_bs1.test(i) == false && _bs2.test(i) == true)
				{
					cout << i << endl;
				}
			}
		}

	private:
		bitset<N> _bs1;
		bitset<N> _bs2;
	};

二、布隆过滤器

1.概念

在进行字符串去重时,如果只将字符串通过哈希算法转换为整型值,不可避免地会出现相同码值的字符串,就会造成误判,这时就需要引入布隆过滤器;
布隆过滤器是使用多个哈希函数,将一个数据映射到位图结构的多个位中;
在这里插入图片描述
布隆过滤器判断数据是否存在,对于存在的判断是不准确的,存在误判的可能;对于不存在的判断是准确的,不会出现误判;
可以通过一个数据多映射几个位,来达到降低误判率的效果,但是误判几率不会降低到0;

2.布隆过滤器的实现

struct HashBKDR
{
	// BKDR
	size_t operator()(const string& key)
	{
		size_t val = 0;
		for (auto ch : key)
		{
			val *= 131;
			val += ch;
		}

		return val;
	}
};

struct HashAP
{
	// BKDR
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (size_t i = 0; i < key.size(); i++)
		{
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ key[i] ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ key[i] ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

struct HashDJB
{
	// BKDR
	size_t operator()(const string& key)
	{
		size_t hash = 5381;
		for (auto ch : key)
		{
			hash += (hash << 5) + ch;
		}

		return hash;
	}
};

template<size_t N,
	class K = string, class Hash1 = HashBKDR, class Hash2 = HashAP, class Hash3 = HashDJB>
	class BloomFilter
{
public:
	void Set(const K& key)
	{
		size_t hash1 = Hash1()(key) % (_ratio * N);//哈希函数算出来的位置并不一定在已经开好的数据范围内,所以还要取模一下,把对应的bit位set成1
		_bits->set(hash1);

		size_t hash2 = Hash2()(key) % (_ratio * N);
		_bits->set(hash2);

		size_t hash3 = Hash3()(key) % (_ratio * N);
		_bits->set(hash3);
	}

	bool Test(const K& key)
	{
		size_t hash1 = Hash1()(key) % (_ratio * N);
		if (!_bits->test(hash1))
		{
			return false;//准确的
		}

		size_t hash2 = Hash2()(key) % (_ratio * N);
		if (!_bits->test(hash2))
		{
			return false;//准确的
		}

		size_t hash3 = Hash3()(key) % (_ratio * N);
		if (!_bits->test(hash3))
		{
			return false;//准确的
		}

		return true;//可能存在误判
	}

private:
	const static size_t _ratio = 5;//平均一个数据开几个bit位
	std::bitset<_ratio* N>* _bits = new std::bitset<_ratio* N>;
};

3.布隆过滤器的删除

布隆过滤器如果想删除一个元素,就需要把这个元素对应的几个位全部置0,这种操作有可能会影响到其他数据的查找;
可以使用多个位表示一个数据的布隆过滤器,每个位被选中后,次数就+1,删除时该位的次数就-1,这样一个数据的删除就不会影响到其他数据了;
但是如果这样设计,就会消耗更多的空间,布隆过滤器的优势就削弱了;

三、哈希切分

例一

在这里插入图片描述
如果要求精确算法,布隆过滤器是不能够满足要求的,此时需要用到哈希切分;
在这里插入图片描述
分别读取两个文件的内容,使用哈希函数将文件中的query分成n个小文件;
在这里插入图片描述
相同的query一定会进入相同编号的小文件,这时我们只需在两个文件的对应编号的小文件中找交集就行了;
在这里插入图片描述

例二

给一个超过100G大小的log file,log中存着ip地址,设计算法找到出现次数最多的IP地址;

在这里插入图片描述
同样可以使用哈希切分来将大文件切分为小文件,那么相同的IP地址一定会进入相同的小文件中,我们只需依次使用map<string, int>对每个小文件统计次数即可;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值