哈希表——位图

之前我们已经了解了哈希表的底层实现,今天我们来了解一下哈希表思想的衍生产物——位图

基本概念

在了解位图之前,我们先来了解一些简单的概念。
我们都知道,计算机的数据和指令都是二进制的:
在这里插入图片描述二进制里面只有0,1两个数字,这就意味着这两个数字可以表示两个不同的状态,位图的基础也是基于这一点。

所以,我们利用这一点。假设我们现在有一堆海量的数据,我可以通过某种方式让数字对应到相应的二进制的数位上,同时规定0表示该数字不存在,1表示存在。这本质上也是一种哈希的思想。

在这里插入图片描述

一道面试题

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。【腾讯】

如果我们硬来,先将这40亿个数存起来,然后二分查找。这无疑是不行的。因为,光是把数据存起来就要用掉16GB的内存,而且要求空间连续。

在这里插入图片描述
但是这时候我们用位图,让位图的每一位对应一个数字,1表示存在,0表示不存在。这样可以大大降低内存的消耗:
在这里插入图片描述
大概我们想要是这样的:
在这里插入图片描述我们所有的位是用来映射存在关系的。

位图实现

我们先来搭搭架子:

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

namespace My_bitmap
{
	//N是需要多少bit位
	template<size_t N>
	class bitmap
	{
	public:

	private:
		vector<int> _bmp;
	};
}

现在我们来计算需要多少内存,然后通过构造函数,开出来相应的空间:

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

namespace My_bitmap
{
	//N是需要多少bit位
	template<size_t N>
	class bitmap
	{
	public:
		bitmap()
		{
			_bmp.resize(N / 32 + 1, 0); //开出相对应的空间,初始化所有位均为0
		}

	private:
		vector <int> _bmp;
	};
}

设置存在或不存在

现在我们有一个数假设为241,我们要把它设置到位图中,将相应的位设置为1。

因为我们以32位为一组,我们把241 / 32 ,可以得到241被分到了哪一个组,然后241 % 32 的到的是241在第几组的第几位:

在这里插入图片描述
现在我们找到了相应的位置,现在我们要将这个位从0变成1,表示241已经存在,但是我们不能影响其他位的状态,这个时候我们要想起来我们的位运算:
我们来实现一下:
在这里插入图片描述

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

			return _bmp[i] |= (1 << j);
		}

好了,我们现在完成了设置,但是现在我想把它又变为0,表示241又不存在了,该怎么办呢?

其实大同小异:
在这里插入图片描述

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

			return _bmp[i] &= (~(1 << j));
		}

检查存在

检查存在和设置存在其实都差不多,只不过把或换成了与:

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

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

我们来测试一下:

#include"bitmap.h"

int main()
{
	My_bitmap::bitmap<10000> _bt;

	_bt.set(241);
	cout << "是否存在:" << _bt.check(241) << endl;
	_bt.unset(241);
	cout << "是否存在:" << _bt.check(241) << endl;

	return 0;
}

在这里插入图片描述

解决一开始的问题

现在我们封装好了一个位图,但是我们只是解决了数据的储存,我们还没有解决如何只找出出现一次的数。

其实我们发现,这里面有三种状态:一次都没有出现的,出现一次,出现一次及其以上

我们发现,这也表示状态,这样我们可以用我们上面位图再封装一个位图,这个位图用来寻找只出现了一次的数:

要表示三种状态,我们需要两个位图:
在这里插入图片描述

	//N是需要多少bit位
	template<size_t N>
	class twobit_map
	{
	public:
		void set(size_t x)
		{
			if (_bt1.check(x) == false && _bt2.check(x) == false)
			{
				_bt2.set(x);
			}
			else if (_bt1.check(x) == false && _bt2.check(x) == true)
			{
				_bt1.set(x);
				_bt2.unset(x);
			}
		 }

		void Print()
		{
			for (size_t i = 0; i < N; i++)
			{
				if (_bt1.check(i) == false && _bt2.check(i) == true)
					cout << "出现一次:" << i << endl;
			}
		}
	private:
		bitmap<N> _bt1;
		bitmap<N> _bt2;
	};

测试一下:

	My_bitmap::twobit_map<241> _bt;

	int a[] = { 12,34,45,67,12,90,90,45 };

	for (auto e : a)
	{
		_bt.set(e);
	}

	_bt.Print();

在这里插入图片描述我们依次类推,还可以找出出现了一次和两次的数:

	template<size_t N>
	class twobit_map
	{
	public:
		void set(size_t x)
		{
			if (_bt1.check(x) == false && _bt2.check(x) == false)
			{
				_bt2.set(x);
			}
			else if (_bt1.check(x) == false && _bt2.check(x) == true)
			{
				_bt1.set(x);
				_bt2.unset(x);
			}
			else if (_bt1.check(x) == true && _bt2.check(x) == false)
			{
				_bt2.set(x);
			}
		 }

		void Print()
		{
			for (size_t i = 0; i < N; i++)
			{
				if (_bt1.check(i) == false && _bt2.check(i) == true)
					cout << "出现一次:" << i << endl;
				else if (_bt1.check(i) == true && _bt2.check(i) == false)
					cout << "出现两次" << i << endl;
			}
		}
	private:
		bitmap<N> _bt1;
		bitmap<N> _bt2;
	};

这样,我们可以灵活运用位图完成数据的查找,对于40亿的数据我们只需要开足够大的空间即可:

My_bitmap::twobit_map<0xffffff> _bt;

或者

My_bitmap::twobit_map<-1> _bt;

不过这个方法的保证自己写的代码中都使用的是无符号数,不然会有一点问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值