资源限制类技巧

资源限制技巧汇总

  1. 布隆过滤器用于集合的建立与查询,并可以节省大量空间
  2. 一致性哈希解决数据服务器的负载管理问题
  3. 利用并查集结构做岛问题的并行计算
  4. 哈希函数可以把数据按照种类均匀分流
  5. 位图解决某一范围上数字的出现情况,并可以节省大量空间
  6. 利用分段统计思想、并进一步节省大量空间
  7. 利用堆、外排序来做多个处理单元的结果合并
技巧4

32位无符号整数的范围是0~4,294,967,295,现在有一个正好包含40亿个无符号整数的文件,
可以使用最多1GB的内存, 怎么找到出现次数最多的数?

直接用哈希表肯定不行,因为一个key和一个valve组成的一组键值对需要用到8B,1GB内存即便全部分配给这个哈希表用,并且不考虑索引开销,那也最多只能存储1250万条记录,一条记录表示一种数,那最多才表示1250万种数。40亿个数最差情况是40亿种数。所以直接做不可能实现,所以要用到哈希函数做分流

我们再保守点估计,假设哈希表最多能存储800万条记录,也就是说可以记录800万种数,

40 × 1 0 8 8 × 1 0 6 = 500 \frac{40\times10^8}{8 \times 10^6} = 500 8×10640×108=500 然后对这40亿个数都通过哈希函数算出哈希值后再模上 500 500 500 ,这样就将所有的数分流到了 500 500 500 个小文件中。这是利用了哈希函数的均匀离散性,40亿个数中所有不同种类的数求出哈希值后一定是比较均匀离散的,取模操作后依然是均匀离散的,这就意味着500个文件中数的种类是差不多的,并且同一种数必然全部都会集中在一个文件中,不可能说9出现了13次,然后6个分到了44号文件,剩下7个9跑到了别的文件。这样我们就保证了任意一个小文件中数的种类数都不会超过800万,即便超过一点也不会超过内存限制,毕竟800万是我们保守之后再保守的决定。

现在我们只需要一个哈希表就行,分别统计一个文件中出现次数最多的数,用变量保存,然后就可以清空当前哈希表,再对下一文件进行统计;每次换新文件都是复用这个哈希表,最后就能得到结果。

技巧5

32位无符号整数的范围是0~4,294,967,295,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数。可以使用最多1GB的内存,怎么找到所有未出现过的数?

如果直接拿HashSet将0~2^32-1都存入HashSet,然后遍历这40亿个数,如果set中有就删除,到最后剩下的就是没出现过的,但是HashSet一开始不可能存得下2^32个数。

这里就要使用位图,一个就是一个bit,用一个bit代表一个数,那么 2 32 2^{32} 232 个数就要使用 2 32 2^{32} 232 个bit,换算一下就是: 2 32    b i t 8 × 2 30 = 0.5 G B \frac{2^{32}\;bit}{8 \times 2^{30}}=0.5GB 8×230232bit=0.5GB 这样就可以了。遍历40亿个数,每个数出现了就让其对应的位设为1,到最后值为0的位对应的数就是没出现过的。

位图如何构造呢?用基本类型int就可以构造。一个int型数据是4个字节,就是32位,那么我们就需要:

2 32 32 = 2 27 \frac{2^{32}}{32}=2^{27} 32232=227 个int型数来表示,所以就需要申请一个 2 27 2^{27} 227 大小的int型数组。如果来了一个数x,我们怎么设置其对应的位为1呢?用以下公式: x / 32 x/32 x/32 可以得到x应该在哪个int型元素, x % 32 x\%32 x%32 可以得到在这个元素的第几位。找到位置后就可以将其值设为1了,用以下代码设置值:

int[] bitMap = new int[1<<27];
int x = 4194893231; //随便写了一个值,不用管具体是多少
int pos = x / 32;
int bit = x % 32;
// 先取出目标位置的值。如果 bitmap[pos] & (1 << bit) == 0 说明目标位置为0,否则为1 
int status = (bitmap[pos] & (1 << bit)) == 0 ? 0 : 1;
// status=1说明已经有过了就不需要重复设置了
if(status == 0)  // 只有等于0才说明第一次来,那就要设置一下
	bitMap[pos] |= (1 << bit);  //或操作就可以实现加1

将40亿个数处理完后,现在要从bitMap中找出哪些位为0了,怎么找?同样用上述的除法、取模操作就能得到目标位置,然后通过第六行代码就能取出目标位置的值。

技巧6

32位无符号整数的范围是0~4,294,967,295,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数。可以使用最多3KB的内存,只用找到一个未出现的数即可。

如果是3KB的内存限制肯定不能用上面的方法做。此时就要利用分段统计思想

3KB最多能申请一个 768 768 768 长度的int型数组,因为一个int型数据是4B。我们取一个不超过 768 768 768 但是是 2的次幂的数,于是选到了 512 512 512 。我们申请一个长度为 512 512 512 大小的int型数组。整个范围是从 0... 2 32 − 1 0...2^{32}-1 0...2321 ,所以范围大小是 2 32 2^{32} 232 ,让这 512 512 512 个数瓜分整个范围,每个数管辖的范围大小是: 2 32 512 = 2 23 \frac{2^{32}}{512}=2^{23} 512232=223 ,也就是说

arr[0]统计 0... 2 23 − 1 0...2^{23}-1 0...2231 ,arr[1]统计 2 2 3... 2 24 − 1 2^23...2^{24}-1 223...2241 这么依次累计下去。每次一个数x到来,只需要 x / 2 23 x/2^{23} x/223 ,就知道是哪个数在管这个范围,然后让这个数加1即可,也就是说数组中的每个元素是统计范围中出现的数的次数。因为这40亿个数是少于 2 32 2^{32} 232 的,所以遍历完文件后,数组中必然有元素的值不等于 2 23 2^{23} 223 ,也就是说这个范围内是有数没出现过的,只需要找到数组中一个不等于 2 23 2^{23} 223 的数即可。

然后将这个元素记录的范围再等分成 512 512 512 份,分给这个数组,数组之前记录的东西都可以清空了,从而实现数组的复用,用不了几次就可以找到。

再拓展一下

32位无符号整数的范围是0~4,294,967,295,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数。只能使用有限几个变量,只用找到一个未出现的数即可。

有限几个变量的内存占用肯定要比3KB还要小很多

方法的思想和上面的一样。现在让L表示左边界0,R表示右边界 2 32 − 1 2^{32}-1 2321 ,求出 m i d = L + ( R − L ) > > 1 mid=L +(R-L)>>1 mid=L+(RL)>>1 ,再申请一个变量 left 记录 [ L , m i d ] [L,mid] [L,mid] 之间的数出现的次数,right 记录 [ m i d + 1 , R ] [mid+1,R] [mid+1,R] 上出现的数的次数。遍历完文件后,必然 left、right 至少有一个不等于 2 31 2^{31} 231 ,只需要选其中一个范围,然后求出新的 L R m i d L \quad R \quad mid LRmid , 然后再在这个小范围上统计。

下面就不是按技巧了,就是随便一道题,看看应该怎么解决

32位无符号整数的范围是0~4294967295,现在有40亿个无符号整数,可以使用最多1GB的内存,找出所有出现了两次的数。

现在要用升级版的位图,用两位表示一个数,那么40亿个数就需要1G多的空间,所以还需要用分段统计的方法,第一次遍历时后半范围的数直接跳过,先统计前半范围,第二次遍历时再统计后半范围。

00 00 00 表示一个数从来没出现过, 01 01 01 表示出现了一次。。 11 11 11 表示出现了三次,当下次来了一个数,找出其目标位置的数值已经是 11 11 11 了,那就不用再管了,直接跳过。

32位无符号整数的范围是0~ 4294967295,现在有40亿个无符号整数,可以使用最多3KB的内存,怎么找到这40亿个整数的上中位数?

思想和上面的技巧6第一题一样,找到512,申请一个512长度的int型数组,每个元素统计 2^23 范围大小的数,进行词频统计,当遍历完整个文件后各个范围上的数出现的次数都统计好了。我们要求的是40亿个数的上中位数,也就是刚好第20亿大的数就是目标值。我们对整个数组元素进行累加,找到那个可能包含第20亿大的区间对应的元素位置i,之前0~i-1的累加和为x,那么我们就要在目标元素i对应的区间上找到第 20亿-x 大的数即可。将该区间取出,然后再分成512份,再进行统计,就可以了。

技巧7

32位无符号整数的范围是0~4294967295,有一个10G大小的文件,每一行都装着这种类型的数字,整个文件是无序的,给你5G的内存空间,请你输出一个10G大小的文件,就是原文件所有数字排序的结果。

这里完全就是堆的相关问题,所以说明堆结构非常重要

用一个Record类有两个成员变量,一个表示数,一个表示已出现的次数。一个Record需要 8B,5G内存最多放 5 × 2 27 5 \times 2^{27} 5×227 个,记录,为了保险,以及索引开销,我们不如直接取 2 16 2^{16} 216 ,这样我们就构建一个大小为

2 16 2^{16} 216 的Record型数组来构建大根堆,遍历整个文件,如果堆没满就直接填,如果堆中已经存在了该元素,那就让对应的Record中的词频加1,如果堆满了,并且又来了堆中不存在的数,如果他大于堆中当前最大值,那就让他替换这个最大值,这样遍历完后,堆中排序好的就是整个文件中从第1小到 2 16 + 1 2^{16}+1 216+1 小的数,并用max记录堆中此时最大的值,把堆中这些数写入结果文件中,然后清空堆,再遍历一遍文件,不过这时如果碰到了 <= max 的数就直接跳过不用管,因为之前就处理过了,于是第二遍遍历又能搞定一批数,循环下去,直到有一次遍历完后,堆的大小不满时就说明可以结束了,是最后一次循环。

32位无符号整数的范围是0~4294967295,有一个10G大小的文件,每一行都装着这种类型的数字,整个文件是无序的,给你1G的内存空间,找出出现次数最多的前100名,包含并列的情况下输出前100即可。

核心思想就是利用哈希分流。然后每个组内的数,建立大小为100的Record型数组构建大根堆,找出其出现最多的100个数,每个组都有一个这样的大根堆。然后再将所有堆中的堆顶元素拿出来再组建一个大根堆,此时的堆顶元素就是全局出现最多的元素,让其出堆,并且让该元素所属的组内堆的下一个大的元素进入这个顶部大根堆,重新排序,此时的堆顶就是全局最大,再出堆,让这个元素所属的组内大根堆再选出最大值进入这个顶部大根堆,依次类推,弹出100个出现最多的数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值