【C++】位图


1. 位图

1.1 位图的概念

位图,就是用二进制位来表示数据的某种状态,例如判断数据是否存在,二进制位为1说明在,二进制位为0说明不在。位图适用于大量无重复的数据。
在这里插入图片描述

1.1 位图的实现

  1. 例子

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

分析
40亿个整形=160亿个字节≈16G,将16G的数据放到内存,空间显然是不太够的,所以更不用考虑遍历、排序+二分查找、用set存储。既然是判断在不在,就可以不需要存储整形。开2 ^ 32个比特位(2 ^ 29个字节 ≈ 0.5G)标记对应值在不在,在就将该比特位标记为1,不在就将比特位标记为0。

  1. 模拟实现
namespace zn
{
	//非类型模板参数,将要开的bit位数传过来
	template<size_t N>
	class bitset
	{
	public:
		//将空间开好,不建议扩容
		bitset()
		{
			//记得向上取整,否则空间开辟不够
			_v.resize(N / 32 + 1);
		}

		//set将n映射的位置置为1
		void set(size_t n)
		{
			size_t i = n / 32;//在第i个整形
			size_t j = n % 32;//在这个整形的第几个位上面

			//如何将某bit位置1
			_v[i] |= (1 << j);
		}

		//reset将n映射的位置置为0
		void reset(size_t n)
		{
			size_t i = n / 32;
			size_t j = n % 32;

			//如何将某bit位置0
			_v[i] &= ~(1 << j);
		}

		//判断这个数在不在->判断某bit位为1还是为0
		bool test(size_t n)
		{
			size_t i = n / 32;
			size_t j = n % 32;

			//其他位都为0,只判断第j位是否为1
			return _v[i] & (1 << j);
		}
	private:
		vector<int> _v;
	};
}

1.3 位图的应用

//1. 给定100亿个整数,设计算法找到只出现一次的整数(整形最多只有42亿多个,所以肯定有重复的)?
//法一:用两个bit位记录出现次数:01(一次),10(两次),11(三次),这样一个整形只能标记16个数据出现次数
//法二:开两个位图,对应bit位表示出现次数:01(一次),10(两次),11(三次)
template<size_t N>
	class twobit
	{
	public:
		void set(size_t N)
		{
			//00 -> 01
			if (!_b1.test(N) && !_b2.test())
			{
				_b2.set(N);
			}
			//01 -> 10
			else if (!_b1.test(N) && _b2.test(N))
			{
				_b1.set(N);
				_b2.reset(N);
			}
			//超过两次的就没必要记录
		}

		bool is_once(size_t N)
		{
			return !_b1.test(N) && _b2.test(N);
		}
	private:
		bitset<N> _b1;
		bitset<N> _b2;
	};


//2. 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
//法一:开两个位图,将文件一映射到位图一,将文件二映射到位图二,遍历其中一个文件,如果两个位图对应bit位都为1,说明是交集
//法二:一个文件所有值映射到一个位图,另一个文件判断在不在,出来的交集需要去重 
//3. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
//与第一题做法类似,用两个位图表示,找出出现次数为0的和出现次数为1的数目,相加即可。


2. 布隆过滤器

如果文件的内容是字符串,怎么办?可以将字符串转换成整形,然后在位图对应位置置为1。但由于无论怎么转换,总会有相同的整形值,然后发生冲突,导致误判。所以就有布隆过滤器。

2.1 概念

布隆过滤器,是用多个哈希函数,将一个数据映射到位图的多个位置,从而实现降低冲突概率。特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”(判断一个值在可能存在误判,因为其他数据的映射位置可能与该值重叠;判断一个值不在是准确的,通过该值得到的映射位置只要有一个为0,说明该值不在)。
在这里插入图片描述

2.2 模拟实现

#include<bitset>
#include<string>
using namespace std;

struct BKDRHash
{
    size_t operator()(const string& str)
    {
        size_t hash = 0;
        for (auto ch : str)
        {
            hash = hash * 131 + ch;
        }

        //cout <<"BKDRHash:" << hash << endl;
        return hash;
    }
};

struct APHash
{
    size_t operator()(const string& str)
    {
        size_t hash = 0;
        for (size_t i = 0; i < str.size(); i++)
        {
            size_t ch = str[i];
            if ((i & 1) == 0)
            {
                hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
            }
            else
            {
                hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
            }
        }

        //cout << "APHash:" << hash << endl;
        return hash;
    }
};

struct DJBHash
{
    size_t operator()(const string& str)
    {
        size_t hash = 5381;
        for (auto ch : str)
        {
            hash += (hash << 5) + ch;
        }

        //cout << "DJBHash:" << hash << endl;
        return hash;
    }
};

template<size_t N,class K = string,class HashFunc1 = BKDRHash,class HashFunc2 = APHash,class HashFunc3 = DJBHash>
class BloomFilter
{
public:
    //将三个哈希函数映射的位置置1
    void Set(const K& key)
    {
    	//HashFunc1()是匿名对象
        size_t num1 = HashFunc1()(key) % N;
        size_t num2 = HashFunc2()(key) % N;
        size_t num3 = HashFunc3()(key) % N;
        _bs.set(num1);
        _bs.set(num2);
        _bs.set(num3);
    }
    //查询key是否在布隆过滤器
    bool Test(const K& key)
    {
        //只要有一个映射位置为0,说明key不在
        if (_bs.test(HashFunc1()(key)%N) == false)
        {
            return false;
        }
        if (_bs.test(HashFunc2()(key)%N) == false)
        {
            return false;
        }
        if (_bs.test(HashFunc3()(key)%N) == false)
        {
            return false;
        }
        //即使三个映射位置都为1,也可能存在误判
        return true;
    }
private:
    bitset<N> _bs;
};

注意
布隆过滤器不支持reset,因为可能会影响到其他字符串的映射(将该位置置为0后,其如果其他字符串的映射位置也是该位置,这会影响以后的查找)。如果硬要支持reset,可以用多个比特位标识一个值作为计数,但这就削弱图和布隆的优势,本来就要处理大量数据,现在空间又变少,还有可能引发计数回绕(0减1后会回到该类型的上限,所有比特位为1)。

2.3 优点和缺点

  1. 优点

(1)查询和插入效率非常高,时间复杂度是O(K),K取决于哈希函数的个数。
(2)处理大量数据,特别是字符串。
(3)节省空间,空间利用率高。
(4)使用同一组散列函数的布隆过滤器可以进行交、并、差运算。

  1. 缺点

(1)不能进行删除操作。
(2)可以通过位图判断是否存在,但不能得到元素本身。
(3)存在误判。

2.4 应用场景

// 布隆过滤器的应用
// 题目:给两个文件A和B,分别有100亿个query(查询,每个query所用的字节不一定相同),我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法
// 
// 思路:创建1000个Ai小文件和1000个Bi小文件(i = 0,1,2,……,999),分别从A和B中读取query,通过哈希映射到Ai和Bi文件中,i = HashFunc(query)%1000,i是多少,query就进入第i个小文件。如果Ai和Bi非空,交集就是Ai和Bi。
// 
// 方法:哈希切分:A和B中相同query一定会分别进入Ai和Bi编号相同的小文件。
// 
// 问题:找交集,Ai读出来放到一个set,依次读取Bi的query在不在set中,在就是交集并且删掉。但还有一个问题,此方法是哈希切分不是平均切分,如果冲突太多或者相同的query太多,会导致某个Ai文件太大,甚至超过1G,怎么办?
//
// 解决方案:1.先把Ai的query读到一个set,如果set的insert报错抛异常(bad_alloc),那么说明Ai中
// 大多数query都是冲突的,此时只需换一个HashFunc进行二次切分,再读取Bi的query判断在不在set中,在就是交集。如果Ai中query能够全部读出来,说明Ai中大多数query都是相同的,此时放到set已经去重,再读取Bi的query判断在不在set中,在就是交集。

2.5 哈希切分的应用

// 哈希切分
// 给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?
// 思路:进行哈希切分,i = HashFunc(query)%1000,相同ip一定会进入同一个小文件,再用map
// 去分别统计每一个小文件中ip出现次数,就可以得到ip出现次数最多,或者出现次数最多的前K个。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值