C++ 哈希思想应用:位图,布隆过滤器,哈希切分

C++ 哈希思想应用:位图,布隆过滤器,哈希切分

一.位图

1.位图的概念

1.问题

给你40亿个不重复的无符号整数,没排过序.给一个无符号整数,如何快速判断一个数是否在这40亿个数中?

2.分析

1 Byte = 8 bit
1KB = 1024 Byte
1MB = 1024KB = 10241024 大约= 10的6次方Byte
1GB = 1024MB = 1024
10的6次方 大约= 10的9次方Byte = 10亿字节
因此4GB 约等于40亿字节

其实最快的方式就是记住1GB约等于10亿字节,这种题就好算了

我们知道40亿个整数,大概就是16GB
如果用排序+二分,
排序需要开16GB大的数组,就算用外排序(归并排序)排完序了,但是二分也需要数组啊…

如果用AVL树红黑树和哈希表
红黑树:三叉链结构+颜色 AVL树:三叉链结构+平衡因子 哈希表:负载因子每个节点的next指针等问题
内存当中更存不下

因此就需要用到位图了

3.位图的概念

在这里插入图片描述

4.演示

假设我们的位图使用一个char类型的数组实现的话
我们这个arr数组的最大值是22,因此只需要22个比特位即可
因此我们用一个char类型的数组,数组中有3个char即可
存放之前:
在这里插入图片描述
存放方式:
在这里插入图片描述
存放过程:
在这里插入图片描述
存放完毕后:
在这里插入图片描述

2.位图的操作

位图的三个核心操作:
set将x对应的比特位设置为1

将某一个比特位置为1,同时不影响其他比特位:
按位或一个数,这个数对应的那个比特位为1,其余比特位为0

void set(size_t x)
{
   
	size_t i = x / 8;
	size_t j = x % 8;
	_bits[i] |= (1 << j);
}

reset将x对应的比特位设置为0

将某一个比特位置为0,同时不影响其他比特位:
按位与一个数,这个数对应的那个比特位为0,其余比特位为1

void reset(size_t x)
{
   
	size_t i = x / 8;
	size_t j = x % 8;
	_bits[i] &= ~(1 << j);
}

test检查x在不在

跟一个数进行按位与
按位与一个数,这个数对应的那个比特位为1,其余比特位为0
如果结果
为0:说明不存在,
不为0说明存在

bool test(size_t x)
{
   
	size_t i = x / 8;
	size_t j = x % 8;

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

3.位图的实现

1.char类型的数组

实现了set reset test之后
位图其实就已经实现完毕了

namespace wzs
{
   
	// N是需要多少比特位
	template<size_t N>
	class bitset
	{
   
	public:
		bitset()
		{
   
			_bits.resize(N/8+1, 0);
		}
	
		void set(size_t x)
		{
   
			size_t i = x / 8;
			size_t j = x % 8;
			_bits[i] |= (1 << j);
		}
	
		void reset(size_t x)
		{
   
			size_t i = x / 8;
			size_t j = x % 8;
			_bits[i] &= ~(1 << j);
		}
	
		bool test(size_t x)
		{
   
			size_t i = x / 8;
			size_t j = x % 8;
	
			return _bits[i] & (1<<j);
		}
	private:
		vector<char> _bits;
	};
}

2.int类型的数组

也可以采用int类型的数组来搞
此时就不是除8模8了
而是除32模32了
因为一个int类型有32个比特位

namespace wzs
{
   
	// N是需要多少比特位
	template<size_t N>
	class bitset
	{
   
	public:
		bitset()
		{
   
			//_bits.resize(N/32+1, 0);
			_bits.resize((N>>5) + 1, 0);
		}

		void set(size_t x)
		{
   
			size_t i = x / 32;
			size_t j = x % 32;
			_bits[i] |= (1 << j);
		}

		void reset(size_t x)
		{
   
			size_t i = x / 32;
			size_t j = x % 32;
			_bits[i] &= ~(1 << j);
		}

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

			return _bits[i] & (1<<j);
		}
	private:
		vector<int> _bits;
	};
}

3.解决一开始的问题

因此对于一开始的那个问题:
在这里插入图片描述

位图开多大呢?

注意: 使用位图,且没有指定范围时,我们要按照该数据的范围大小来开位图

无符号整数的范围是0~4,294,967,295
因此我们需要开0~4,294,967,295大的范围

0~2的32次方-1的范围
一共有2的32次方个整数,

2的10次方是1024
2的30次方就是1024*1024*1024=1024*1024K=1024M=1G
因此2的32次方就是4G个整数

而我们使用一个比特位表示一个整数的,一个字节有8个比特位
因此我们只需要4G/8=0.5G个字节即可

因此我们的位图大小就是0.5G,正常情况下内存当中完全能存的下
无需担心

小小补充

而这个数字也不好记,其实它还有下面3种写法

  1. (size_t)-1 将-1强转为无符号整形
  2. UINT_MAX (unsigned_intMAX)
  3. 0xffffffff(16进制:8个f)
  4. pow(2,32)-1

一定注意:^在C++/C当中是异或,不是幂
因此(2^32)-1不等于那个数字

pow的返回值类型是double类型
在这里插入图片描述

验证

下面我们来验证一下位图能否完成这一任务
在这里插入图片描述
在这里插入图片描述
成功完成这一任务

4.位图的应用

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

1.位图开多大?

我们先算一下100亿个整数要占多少G的内存?
一个比特位映射一个整数,一个字节有8个比特位
100亿个整数=100亿个比特位=100亿/8个字节=12.5亿字节=1.25G

我们真的要用1.25G的空间吗?
并不是!!!
而是刚才我们算的0.5G就足以

因为我们只存范围
如果要求必须使用位图来做,就算只有2个整数,不给我们范围,还是要用0.5G大小的位图

2.思路

找到只出现一次的整数,因为一个比特位只有0和1这两种状态,因此无法表示出现了1次以上的数字的状态

那么怎么办?
如果用2个比特位来表示一个整数的状态呢?
00就是出现0次
01就是出现1次
10就是出现2次
出现2次以后这个数我们就不再统计次数了

因此我们可以:
1.修改上面的位图,用2个比特位来表示一个整数
此时位图的大小就要乘以2,成为1G
2.用2个位图来做,每个位图依然是0.5G
只不过set,test函数要修改一下即可

下面我们就按照第2种来做吧,这个清晰易懂

3.代码

因为我们没有统计2次以上的次数,因此我们不允许进行reset操作

//利用组合来进行封装
template<size_t N>
class two_bitset
{
   
public:
	void set(int x)
	{
   
		//00 -> 01
		if (_bits1.test(x) == false && _bits2.test(x) == false)
		{
   
			_bits2.set(x);
		}
		//01 -> 10
		else if (_bits1.test(x) == false && _bits2.test(x) == true)
		{
   
			_bits1
评论 82
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

program-learner

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

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

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

打赏作者

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

抵扣说明:

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

余额充值