【 C++ 】bitset位图的模拟实现

本文介绍了如何使用位图数据结构解决一个面试问题,即快速判断40亿个无序整数中是否存在特定数,通过bitset模板类实现位的设置、重置、测试和翻转操作,有效降低时间复杂度。
摘要由CSDN通过智能技术生成

位图概念

曾经有这样一个面试题,如果给你40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。【腾讯】方法如下:

  1. 遍历,时间复杂度O(N)。
  2. 排序(O(NlogN)),利用二分查找: logN。
  3. 位图解决。

数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。比如:

在这里插入图片描述

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

bitset大体框架

  • bitset能实现对数字的位的操作,同时也能通过类似于数组的下标来访问各个位的数值,以执行相应的操作。模拟bitset就是用一个普通的数组来存储数据以达到模拟的目的。
  • 如果我们以一个整型作为比特位的容器,那么如果要求0~N范围的比特位,就需要有N/32+1 个整型来容纳这些比特位,同理如果以char为容器,则需要N/8+1个char来容纳N个比特位。这里我们用vector数组作为底层容纳比特位的容器,且其存储的数据类型为char。
#pragma once
#include<iostream>
#include<vector>
using namespace std;
namespace flash
{
	//N个比特位的位图
	template<size_t N>
	class bitset
	{
	public:
		//构造函数
		bitset()
		{
			//+1保证足够比特位,最多浪费8个比特位
			_bits.resize(N / 8 + 1, 0);
		}
		//把x映射的位标记成1
		void set(size_t x)
		{
			//x映射的比特位在第几个char对象
			size_t i = x / 8;
			//x在char第几个比特位
			size_t j = x % 8;
			//利用按位或|把第j位标记成1
			_bits[i] |= (1 << j);
		}
		//把x映射的位标记成0
		void reset(size_t x)
		{
			//x映射的比特位在第几个char对象
			size_t i = x / 8;
			//x在char第几个比特位
			size_t j = x % 8;
			//将1左移 j 位再整体反转后与第 i 个char进行与运算
			_bits[i] &= (~(1 << j));
		}
		//判断指定比特位x的状态是否为1
		bool test(size_t x)
		{
			//x映射的比特位在第几个char对象
			size_t i = x / 8;
			//x在char第几个比特位
			size_t j = x % 8;
			//将1左移 j 位后与第 i 个char类型进行与运算得出结果
			return _bits[i] & (1 << j);
		}
		//翻转指定位x
		void flip(size_t x)
		{
			//x映射的比特位在第几个char对象
			size_t i = x / 8;
			//x在char第几个比特位
			size_t j = x % 8;
			//将1左移 j 位后与第 i 个char进行按位异或运算^即可。
			_bits[i] ^= (1 << j);
		}
		//获取位图中可以容纳位N的个数
		size_t size()
		{
			return N;
		}
		
		//统计set中1的位数
		size_t count()
		{
			size_t count = 0;
			for (auto e : _bits)
			{
				int n = e;
				while (n)
				{
					int m = n - 1;
					n = n & m;
					count++;
				}
			}
			return count;
		}
		//判断所有比特位若无置为1,返回true
		bool none()
		{
			//遍历每个char
			for (auto e : _bits)
			{
				if (e != 0)//说明有位被置为1,返回false
					return false;
			}
			return true;//说明全为0,返回true
		}
		//判断位图中是否有位被置为1,若有则返回true
		bool any()
		{
			return !none();
		}
		//全部NUM个bit位被set返回true
		bool all()
		{
			size_t size = _bits.size();
			//先检查前N-1个char
			for (size_t i = 0; i < size - 1; i++)
			{
				if (~_bits[i] != 0)//取反应该为0,否则取反之前不全为1,返回false
					return false;
			}
			//再检查最后一个char的前 N%8 个位
			for (size_t j = 0; j < N % 8; j++)
			{
				if ((_bits[size - 1] & (1 << j)) == 0)//和test的原理一致
					return false;
			}
			return true;
		}
	private:
		vector<char> _bits;//位图
	};
}

成员函数

构造函数

  • 一个char类型有8个bit位,所以理想状态下N个比特位的位图就需要用到 N/8 个字节,但仅限于N是8的整数倍,如果N位10,那么计算下来就会少2个比特位,因此综合考虑,我们给出 N/8+1 个字节,这样算下来,所需的N个比特位绝对都能访问到,最多可以整除的情况下浪费了8个比特位(1字节)。

而构造函数,我们只需要对这所有的比特位(N/8+1)个字节的大小初始化为0即可。

//构造函数
bitset()
{
	//+1保证足够比特位,最多浪费8个比特位
	_bits.resize( N/8+1, 0);
}

set函数

set的作用是把x映射的位置标记成1,实现规则如下:

  1. 通过x / 8计算x在第i个char类型 。
  2. 通过x % 8计算x在char第j个比特位 。
  3. 利用按位或 | 把第i个char中的第j个比特位置为1。

在这里插入图片描述

//把x映射的位标记成1
void set(size_t x)
{
	//x映射的比特位在第几个char对象
	size_t i = x / 8;
	//x在char第几个比特位
	size_t j = x % 8;
	//利用按位或|把第j位标记成1
	_bits[i] |= (1 << j);
}

在这里插入图片描述

reset函数

reset的作用是把把x映射的位标记成0,实现规则如下:

  1. 通过 x/8 计算x在第i个char类型 。
  2. 通过 x%8 计算x在char第j个比特位 。
  3. 将1左移 j 位再整体反转后与第 i 个char类型进行与运算即可。

在这里插入图片描述

//把x映射的位标记成0
void reset(size_t x)
{
	//x映射的比特位在第几个char对象
	size_t i = x / 8;
	//x在char第几个比特位
	size_t j = x % 8;
	//将1左移 j 位再整体反转后与第 i 个char进行与运算
	_bits[i] &= (~(1 << j));
}

test函数

test的作用是判断指定比特位x的状态是否为1,实现规则如下:

  1. 通过x / 8计算x在第i个char类型 。
  2. 通过x % 8计算x在char第j个比特位 。
  3. 将1左移 j 位后与第 i个char类型进行与运算得出结果 。
  4. 若结果非0,则该位被设置,否则该位未被设置。

在这里插入图片描述

//判断指定比特位x的状态是否为1
bool test(size_t x)
{
	//x映射的比特位在第几个char对象
	size_t i = x / 8;
	//x在char第几个比特位
	size_t j = x % 8;
	//将1左移 j 位后与第 i 个char类型进行与运算得出结果
	return _bits[i] & (1 << j);
}

flip函数

flip的作用是用于翻转指定位,若指定位为0,翻转后为1,若指定位为1,反转后为0,实现规则如下:

  1. 通过x / 8计算x在第i个char类型。
  2. 通过x % 8计算x在char第j个比特位。
  3. 将1左移 j 位后与第 i 个char进行按位异或运算^即可。

在这里插入图片描述

//翻转指定位x
void flip(size_t x)
{
	//x映射的比特位在第几个char对象
	size_t i = x / 8;
	//x在char第几个比特位
	size_t j = x % 8;
	//将1左移 j 位后与第 i 个char进行按位异或运算^即可。
	_bits[i] ^= (1 << j);
}

count函数

count的作用是统计位图中被设计为1的个数,实现规则如下:

  1. n = n & (n-1) => 消去n的二进制数中最右边的1。
  2. n不为零继续执行第一步。
  3. 执行了几次说明n中有多少个1。

在这里插入图片描述

//统计set中1的个数
size_t count()
{
	size_t count = 0;
	for (auto e : _bits)
	{
		int n = e;
		while (n)
		{
			n = n & (n - 1);
			count++;
		}
	}
	return count;
}

size函数

size的作用是获取位图中可以容纳位N的个数。

//获取位图中可以容纳位N的个数
size_t size()
{
	return N;
}

none any all

  1. none:none的作用是遍历每一个char,如果全为0,则none返回true。
//判断所有比特位若无置为1,返回true
bool none()
{
	//遍历每个char
	for (auto e : _bits)
	{
		if (e != 0)//说明有位被置为1,返回false
			return false;
	}
	return true;//说明全为0,返回true
}
  1. any:any的作用判断位图中是否有位被置为1,若有则返回true,这其实和none的作用刚好相反,因此我们直接复用none即可。
//判断位图中是否有位被置为1,若有则返回true
bool any()
{
	return !none();
}
  1. all:all的作用是判断位图中是否所有的位都被置为1,注意这里的特殊性,先前开辟空间时我们为了避免出现N/8不能整除的情况,特地在resize时多开了一个char类型(8比特)的空间,这8比特里面只有前N%8个比特位才是有效的(剩下的都是多开的空间),因此all函数需要分情况讨论:

    • 先检查前n-1个char的二进制是否为全1。
    • 再检查最后一个char的前N%8个比特位是否为全1。
      在这里插入图片描述
//全部NUM个bit位被set返回true
bool all()
{
	size_t size = _bits.size();
	//先检查前N-1个char
	for (size_t i = 0; i < size - 1; i++)
	{
		if (~_bits[i] != 0)//取反应该为0,否则取反之前不全为1,返回false
			return false;
	}
	//再检查最后一个char的前 N%8 个位
	for (size_t j = 0; j < N % 8; j++)
	{
		if ((_bits[size - 1] & (1 << j)) == 0)//和test的原理一致
			return false;
	}
	return true;
}
  • 44
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值