【数据结构与算法】位图 && 布隆过滤器 && 海量数据问题处理 && 哈希切分


在这里插入图片描述

Ⅰ. 位图

一、问题引入

​ 这里有道题:给 40 亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这 40 亿个数中。这里一共有三种方法:

  1. 遍历,时间复杂度 O(N)
  2. 先排序 O(NlogN),然后利用二分查找:O(logN)

​ 很明显发现前两种方法虽然可行,但是对于 40 亿个整数来说,内存开销是十分的大的,因为 40 亿个整数相当于是 16GB 左右,这个时候也更不可能去使用红黑树这些数据结构来解决,毕竟红黑树这些数据结构本身就带有一些负载的消耗(旋转),所以我们这里给出了第三种方法:位图!

二、位图概念

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

​ 比如上面的面试题,要我们开 40 亿个整数,并且判断一个数是否在这其中,那么我们只需要开辟 500MB 的空间大小,因为我们这里每个比特位代表一个数字存不存在的状态,比如 0 代表不存在,1 代表存在,那么一个比特位就足够啦,于是只需要开原本需要 16GB1/32 即可,也就是 500MB!这就很巧妙的解决了空间问题!

​ 而至于时间问题,查找某个数字的时候,位图也是通过某个数字的对应映射关系查找的,也就是通过哈希!所以时间复杂度为 O(1)

在这里插入图片描述

三、位图的模拟实现

​ 首先我们先把其框架搭出来:

// BitSet.h
#include <iostream>
#include <vector>
namespace liren
{
   
	class BitSet
	{
   
	private:
		std::vector<char> _bits; 
	};
}

这里很奇怪哦,用到的是 vector 里面存的 char,为什么呢?

​ 仔细想一想,c++ 中最小的存储单位就是 char,由于我们要的是按比特位来识别数字,所以我们这里就用 char 类型来表示 8 个比特位,也就是 8 个数字,这样子也方便我们后面的位运算!(用 int 类型来存储也可以,但是一共有 32 个比特位,相比于 char 的话会更难控制一些,所以这里选用 char


​ ❓ 那么我们接下来的拷贝构造函数中如何来初始化呢?

这里我们用模板参数 N 来代表要初始化的比特位个数,那么问题又来了,数组开多大的空间呢❓

​ 🌛 由于我们数组中存放的类型是 char 类型,所以我们要得到 N 个比特位的话,则要 resize(N / 8),这里还有一个细节,就是还要多加一,因为假设 N10 的话,那么 N / 8 其实还是 1,那么我们开出来的就只有一个 char 也就是 8 个比特位,但是 N10 啊,所以为了保险起见,我们每次多开一个 char 的空间!也就是 resize(N / 8 + 1)

// BitSet.h
#include <iostream>
#include <vector>
namespace liren
{
   
	// N代表要存放的比特位数量
	template <size_t N>
	class BitSet
	{
   
	public:
		// 构造函数
		BitSet()
		{
   
			// 每次多开一个字节,并初始化为0
			_bits.resize(N / 8 + 1, 0);
		}
	private:
		std::vector<char> _bits; 
	};
}

下面我们就来实现位图的几个主要的接口函数:(注意这里vs是小端,大端的实现略有不同

set 函数:将 x 位置处的比特位设为 1

​ 方法:先获取到 x 的位置,然后 按位或x 处比特位为 1 的数即可。

// 将x位置处的比特位设为1
void set(size_t x)
{
   
    // 先获取x的位置
    size_t i = x / 8;
    size_t j = x % 8;

    // 接着让x处的比特位(按位或)上(只有x处为1的数)
    // 注意这里vs是小端存储,注意方向
    _bits[i] |= (1 << j);
}

reset 函数:将 x 位置处的比特位设为 0

​ 方法:先获取到 x 的位置,然后让 x 处的比特位 按位与(&)上 x 处比特位为 0 其他位为 1 的数即可。

// 将x位置处的比特位设为0
void reset(size_t x)
{
   
    // 先获取x的位置
    size_t i = x / 8;
    size_t j = x % 8;

    // 接着让x处的比特位(按位与)上(只有x处为0的数)
    // 注意这里vs是小端存储,注意方向
    _bits[i] &= (~(1 << j));
}

test 函数:检查 x 位置处的比特位是否为 1

// 检查x位置处的比特位是否为1
bool test(size_t x)
{
   
    // 先获取x的位置
    size_t i = x / 8;
    size_t j = x % 8;

    // 接着让x处的比特位(按位与)上(只有x处为1的数)
    return _bits[i] & (1 << j);
}

​ 测试代码:

// test.cpp
#include "BitSet.h"

int main()
{
   
	liren::BitSet<20> t;

	t.set(10);
	std::cout << t.test(10) << std::endl;

	t.reset(10);
	std::cout << t.test(10);
	return 0;
}

​ 接下来我们尝试着来解决上面遗留的面试题:

int main()
{
   
    // 40亿个整数,我们只需要开500MB的比特位即可
    // 但是为了保证int的范围都被包括,我们要开42亿整数空间的大小
    // 也就是unsigned的最大值
    // 但其实这里我们直接利用十六进制表示即可开unsigned的最大值
    //liren::BitSet<-1> b; 这种写法会报错

    liren::BitSet<0xffffffff> b;

    b.set(1314);
    std::cout << b.test(1314) << std::endl;

    b.reset(1314);
    std::cout <
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

利刃大大

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

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

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

打赏作者

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

抵扣说明:

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

余额充值