简述位图C/C++

位图的概念

所谓位图,就是使用比特位表示数据存在与否的一种数据结构,1个比特位表示一个数据的状态(存在/不存在)。而一个字节有8个比特这就意味着一个字节就可以表示8个数据的状态,这将大大节省保存数据的空间。

例如,我要保存1-16这16的数字,通常的做法是申请16个整型空间,将其存储起来,这需要16*4=64个字节的空间。而如果使用位图,则只需要2的字节即可:2个字节有16个比特,每个比特有两种状态(0/1),则16个比特就可以表示这16个整型数字。

但很明显,你若是给两个数:1与10000000,想将他们存储起来,用整型数据只需2*4=8个字节即可,用位图则需要就不合适了,由上面的例子可以轻易看出位图需要10000/8=1250个字节了,这就不合适了。由上面的例子可以轻易看出位图适用于那些密度较大的数据,即适用数据断点不是特别大的情况

一般来说,位图适用于海量数据,数据无重复的场景。通常是用来判断某个数据存在不存在的。

若现有40000000个不重复的数据,我们使用位图,仅需要5000000个字节即可表示所有数据的存在状态,使用其他数据结构需要的空间比位图需要的空间往往是8倍往上

位图的简单使用示意

例,现有一组数据A{1,3,4,5,7,8,9,10,13,17,22} 需要表示存在状态,A中的最大数据为22,那么我们需要22/8=2余6,共计3个字节。

所属字节012
表示范围(0,7)(8,15)(16,23)
比特状态01011111110010001000010
从左至右对应{0,1,2,3,4,5,6,7}{8,9,10,11,12,13,14,15}{16,17,18,19,20,21,22,23}

位图的应用

  1. 快速查找某数据是否存在于一个集合中(查探目标数据在bitset中对应比特的状态)
  2. 排序(因bitset自身表示范围有序,因此将数据映射向bitset后,即完成排序)
  3. 求两个集合的交集,并集等(两个集合对应两个bitset,交集则是两个比特bitset按位与的结果,并集则是二者按位或的结果)
  4. 操作系统中磁盘块标记(已占用为1,未占用为0)

位图的实现

位图由于是以比特为单位实现目标,因此位图的所有方法几乎均是位操作

以下代码是从STL中的bitset抽出的部分方法,可以基本完成位图的基本功能,已添上注释

#include <iostream>
#include <vector>
using namespace std;

namespace BL {
	template <size_t _N>
	class bitset {
	public:
		typedef unsigned long _Ty;//无符号长整型
	public:
		bitset() { _Tidy(); }
		bitset<_N>& set(){
			_Tidy(~(_Ty)0);//按位全1
			return (*this);
		}
		bitset<_N>& set(size_t _P, bool _X = true){
			if (_X)//设置_P为比特为1
				_A[_P / _Nb] |= (_Ty)1 << _P % _Nb;
			else//设置_P为比特为0
				_A[_P / _Nb] &= ~((_Ty)1 << _P % _Nb);
			return (*this);
		}
		bitset& flip(){ // 所有比特翻转
			for (size_t _I = 0; _I <= _Nw; ++_I) {
				_A[_I] = ~_A[_I];//按位取反
			}
			_Trim();
			return *this;
		}
		size_t size() const { return (_N); }
		bool test(size_t _P) const{
			//第_P位是否为1,是则true
			return ((_A[_P / _Nb] & ((_Ty)1 << _P % _Nb)) != 0);
		}
		bool any() const{
			//是否存在非0位
			for (int _I = _Nw; 0 <= _I; --_I)
				if (_A[_I] != 0)
					return (true);
			return (false);
		}
		bool none() const { return (!any());}
		size_t count() const{
			//计算1的个数
			size_t _V = 0;
			for (int _I = _Nw; 0 <= _I; --_I)
				for (_Ty _X = _A[_I]; _X != 0; _X >>= 4)
					_V += "\0\1\1\2\1\2\2\3"
					"\1\2\2\3\2\3\3\4"[_X & 0xF];
			//"\0\1\1\2\1\2\2\3"
					//"\1\2\2\3\2\3\3\4"[_X & 0xF];[]之前的字符串其实就是一个常字符数组
			//其具体意义为十进制数_X&0xF对应的二进制中1的个数
			return (_V);
		}
		class reference{
		//之所以有reference类是因为bitset会将[]进行重载,以实现修改存在状态等操作
			friend class bitset<_N>;
		public:
			reference& operator=(bool _X){
				//第_P位改变状态
				_Pbs->set(_Off, _X);
				return (*this);
			}
			reference& operator=(const reference& _Bs){
				_Pbs->set(_Off, bool(_Bs));
				return (*this);
			}
			reference& flip(){
				//取反
				_Pbs->flip(_Off);
				return (*this);
			}
			bool operator~() const{
				//是否为0
				return (!_Pbs->test(_Off));
			}
			operator bool() const{
				//是否为1
				return (_Pbs->test(_Off));
			}
		private:
			reference(bitset<_N>& _X, size_t _P)
				: _Pbs(&_X), _Off(_P) {}
			bitset<_N>* _Pbs;
			size_t _Off;
		};
		reference operator[](size_t _P) { return (reference(*this, _P)); }
	private:
		void _Tidy(_Ty _X = 0) {//重置数据为全0
			for (int _i = _Nw; _i >= 0; --_i)//置为_X
				_A[_i] = _X;
			if (_X != 0)//多出的字节置0(大于_N的字节)
				_Trim();
		}
		void _Trim() {
			if (_N % _Nb != 0) //仅保留合理范围内的1
				_A[_Nw] &= ((_Ty)1 <<  _N % _Nb) - 1;
		}
	private:
		enum {
			_Nb = CHAR_BIT * sizeof(_Ty),//数组单元素所占比特数:32
			_Nw = _N == 0 ? 0 : (_N -1)/ _Nb
			//计算需要几个4字节空间,之所以_N-1的原因在于:
			//_Nw代表可以完全占据一个_Ty空间的数目
			//eg.127,(127-1)/32=3余30,_Nw=1
			//eg.128,(128-1)/32=3余31,_Nw=1
			//之所以使用(_N-1)/_Nb,其思路绝对保证完整_Ty数目,减1之后,
			//剩下的数目必然只有两种可能,刚好一个完整Ty/不足一个完整_Ty
			//将剩下的数目交给申请数组时处理,有归一化处理的涵义
		};
		_Ty _A[_Nw+ 1];//加1补不足
	private:
		friend ostream& operator<<(ostream& _O, const bitset<_N>& _R){
			//重载输出符
			for (size_t _P = _N; 0 < _P;)
				_O << (_R.test(--_P) ? '1' : '0');
			return (_O);
		}
	};
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值