位图的海量数据处理

                                今天给大家带来来分享一下位图的使用 

位图的海量数据处理题目      

通过几道海量数据处理的题目来引出今天的内容。

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

        这一道题肯定会有同学想用二分,先将数进行排序,然后二分。确实,二分是一个非常快速的算法。还有同学会想到用set插入+find查找。也不失为一个方法。但我们首先应该考虑内存的问题,排序需要一个40亿整型数组,set需要不大于40亿个结点。40亿个无符号整型大约有16个G,1个 G大约有10亿字节,40亿unsigned_int类型的数大约160字节也就是16G。正常的电脑内存也可以有16G,但我们还有许多别的程序在运行,如果运行这一个程序就16G,这样我们的电脑会裂开。

      这里有一种新的方法,就是位图。这里就有哈希的思想在里面。位图是将每一个数映射在每一个对应的位上,一个字节是8个比特位,一个整型是32个比特位,也就是说,一个整型的32位能找到对应的32个元素,因此,我们能节省很多的空间。所以,位图是本题最好的解决办法。

40亿字节就需要40亿比特位,约等于0.5G。无符号int 的最大数达到42亿多,也就是2^32次方。这里咱们可以多开一点空间,开大于0.5G多一点。

 如图所示,假如我要把50这个元素映射到第五十个比特位,也是第二个整型中的其中一位。

如何找到某个元素x存储在哪个位置呢?这里我定义两个变量,i 和 j。计算x存储在数组的第i个整型数据中,计算x存储在第j位。 可以以取模的方式来计算。 i=x/32   j=x%32(这里的32是一个整型的32位,我们要计算存储在哪个整型的第几个比特位)。

//置为1
		void set(size_t x)
		{
			assert(x <= N);
			int i = x / 32;
			int j = x % 32;
			_bit[i] = _bit[i] | (1 << j);
		}

这里把元素对应的位映射为1。算法:将该映射位与1向左移j位进行或操作。有些同学会疑惑有些内存是以小端存储,有的以大端存储,如果或了,小端和大端得到的结果不就不一样了吗,这该怎么办呢?

可能有些同学不知道大小端。我来解释一下,一个数据的低位字节存低地址中,高位字节存高地址中这就是小端,大端的情况就反过来了。这里我们存储1,查看存储情况。

这里采用的就是小端存储,看上图,地址的最后2位,74,75,76,77存的是01,00,00,00.

1的32位二进制为00000000 00000000 00000000 00000001这里采用16进制存储,也就是1个16进制数等于4个2进制数,所以按正常的手写是这样的00 00 00 01,从左往右就是从低位字节到高位字节。将手写的和图中的联系起来就知道在内存中怎么存储的了。所以大端的情况就是01 00 00 00,如果这两种不同的情况去或,得到的结果肯定不同。但不要担心,编译器会自己为我们解决这些问题,大家不需要在意编译器是怎么帮我们去实现的,只要知道即可。

//置为0
		void reset(size_t x)
		{
			int i = x / 32;
			int j = x % 32;
			_bit[i] = _bit[i] & ~(1 << j);
		}

如果元素不存在了,可以把元素对应的位映射为0。算法:让该映射位与1左移j位然后取反后的结果进行与操作。

bool test(size_t x)
		{
			assert(x <= N);
			int i = x / 32;
			int j = x % 32;
			return _bit[i] & (1 << j);
		}

这里是测试对应位的元素还在不在,在的话就返回true(1),不在就返回false(0)。

这里是位图的完整实现代码:

	//位图
	template<size_t N>
	class Bitset
	{
	public:
		Bitset()
		{
			_bit.resize(N / 32 + 1, 0);
		}
		//置为1
		void set(size_t x)
		{
			assert(x <= N);
			int i = x / 32;
			int j = x % 32;
			_bit[i] = _bit[i] | (1 << j);
			//return _bot[i] == 0 ? false : true;
		}
		//置为0
		void reset(size_t x)
		{
			int i = x / 32;
			int j = x % 32;
			_bit[i] = _bit[i] & ~(1 << j);
		}
		bool test(size_t x)
		{
			assert(x <= N);
			int i = x / 32;
			int j = x % 32;
			return _bit[i] & (1 << j);
		}
	private:
		vector<int> _bit;
	};
	void test()
	{
		Bitset<100> bs;
		bs.set(11);
		bs.set(19);
		bs.set(30);
		bs.set(45);
		for (size_t i = 0; i < 100; i++)
		{
			if (bs.test(i))
				cout << i << "->" << "在" << endl;
			else
				cout << i << "->" << "不在" << endl;
		}
	}

经过上面的分析,我将继续加大难度,给出一道新题目让同学们继续思考。

2.给定100亿个整数,设计算法找到只出现一次的整数

       这里不知道会不会有同学疑惑,咱们无符号整型最大也才42亿多,100亿个整数是怎么来的,这里明确的说,会有大量的重复的数。我们要做的就是用位图将重复的数映射到相应的位上,同时统计次数。肯定会有同学想到,咱们的位图的每一位不是0就是1,代表出现0次和1次,如果多次重复的数,就不能还用0和1了吧。2次及以上就要有2个二进制数存储了是吧。于是,通过这个想法,我们想可以再多用一个位图不就行了吗。这样最多能统计出现3次的数,但出现超过3次的怎么办?

这个我们看题目,只找到出现一次的数,超过2次的我们就不要管了,如果超过3次,就当把两个二进制映射成11就行了,我们不需要管他出现几次,我们只看出现一次的数。

这里我们把上面那个位图的二进制看成2个二进制的低位,下面的看成高位。这里存50,如果出现1次上(1),下(0),2次上(0),下(1),依此类推。这样我们就能解决这道题目了。这里也只开了1个多G一点的空间。

这里我展示实现的代码:

template<size_t N>
	class two_bit_set
	{
	public:
		void set(size_t x)
		{
			//01
			if (bs1.test(x) == false && bs2.test(x) == false)
			{
				bs2.set(x);
			}    //10
			else if (bs1.test(x) == false && bs2.test(x) == true)
			{
				bs1.set(x);
				bs2.reset(x);
			}
		}
		bool _test(size_t x)
		{
			if (bs1.test(x) == false && bs2.test(x) == true)
				return true;

			return false;
		}
	private:
		Bitset<N> bs1;
		Bitset<N> bs2;
	};
};

这里的代码是基于上面的代码而写的。希望大家都能耐心看一下哈。

3.给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集。  

      根据前几个问题,这个问题就显而易见了。开辟两个位图,将两个文件给定的元素分别映射到相应位图的相应位上,然后将两个位图的同一位置进行&(与),如果与后为1就是交集,为0不是交集。

4.给定100亿个整数,设计算法找到只出现一次的整数,给定0.5G的内存。

      这个题相较前几题又把内存给不断缩小,因此灵活应变,100亿整数有大量重复的数,我们int的最大值为2^32因此我们可以分段映射。当把0~2^31-1的数映射完,并统计他的个数为1的数然后删除,接着统计2^31次方到2^32-1的数,接着重复操作上述步骤。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值