Loading

资源限制类问题的常用解决方案

资源限制类问题的常用解决方案

作者:Grey

原文地址:

博客园:资源限制类问题的常用解决方案

CSDN:资源限制类问题的常用解决方案

问题1

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

主要思路

首先,需要考虑最差情况,假设 40 亿个数都不一样。

如果使用哈希表,key 表示其中的某个数(4 byte),value 表示出现的次数(4 byte),

所以哈希表的每条记录至少需要 8 byte 空间,即:

4Byte + 4Byte = 8Byte

最差情况,40 亿条不同记录进入哈希表,

需要内存空间是:

8Byte * 40亿 = 320亿Byte

约等于 32GB 。内存不够。

所以无法直接使用哈希表来解这个问题。

如果申请数组来存,数组 i 号位置的表示 i 这个值出现的次数。

最差情况,要存下 40 亿个数,数组需要的内存空间是:

40亿 * 4Byte = 160亿Byte

约等于 16G , 内存也不够。

所以,也无法用直接数组来解决这个问题。

所以,我们只能从限制条件入手,

由于只有 1GB 内存,如果用哈希表,一条记录大约 8Byte ,保守估计,1GB 内存可以装下

1G / 8 Byte = 10亿Byte / 8Byte = 1.25亿,即1.25亿条记录,在这个 1.25亿基础上再保守一点,减少到 1千万,

在 1GB 内存限制下,使用哈希表处理 1千万条不同的记录,绝对不会超过现有的内存限制。通过如下计算

40亿 / 1千万 = 400

可以创建 400 个空文件,然后,对于 40亿个数中每一个数 m,通过哈希函数得到一个哈希值,假设 m 的哈希值为 n, 然后用 n 执行如下公式

hash(m) % 400 = n % 400 = i

得到的 i 的值是多少,就把 m 这个值分配到第 i 号文件中。

根据哈希函数的性质,相同的数一定进入同一个文件。 且 400 个文件中每个文件大约都是 1千万种

使用哈希表统计每个文件中出现次数最多的数(最多1千万种左右,哈希表在现有资源限制下无压力),所有文件中出现次数最多的数,就是整个文件出现次数最多的数。(因为同一种数的哈希值一样,分配到的文件一定是同一个!)

问题2

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

如果使用HashSet,大约需要

40亿 * 4Byte = 160亿Byte

大约 16GB ,内存不够。

所以无法直接使用HashSet来解。

本题可以使用位图(bit数组)来存每个数是否出现,0 表示出现, 1 表示没有出现。以 Java 为例,一个 int 类型可以表示 32 位二进制, 因为要标识每个数是否出现过。所以 40亿个数大约需要

40亿 / 4byte = 1000MB

1000MB 空间的内存占用,满足限制条件。

参考如下示例代码(179这个数字是否出现过):

int[] arr = new int[10];
int i = 179;
int status = (arr[i / 32] & (i << (i % 32 ))) == 0 ? 0 : 1

status = 0 则表示没有出现过,status = 1 则表示没有出现过。

原理就是:i 号 bit 表示 i 这个数是否出现过,用 i/32 标识这个 bit 出现在数组的哪个位置上,用 i%32 来标识这个 bit 属于 arr[某位] 的哪个位置上。

位图的详细说明见:位图的使用与实现

问题3

32位无符号整数的范围内有 4294967295 个数,现在有一个正好包含 40亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数, 内存限制为 3KB ,只用找到一个没出现过的数即可。

以 Java 为例,3KB 如果用来表示 long 类型(一个 long 类型是 8byte)的数组,数组最大长度大约是:

3KB / 8Byte = 375

数组长度大约 375, 然后寻找比 375 小的离 375 最近的二的某次方的数,得到 256 , 接下来可以申请一个 256 长度的 long 类型数组,假设叫 arr 。

由于无符号整数数字一共有 2 的 32 次方个,可以将

2^32 / 256 = 16,777,216

均分成 256 份,使用问题2中位图的思路

arr[0] 统计 0 ~ 16,777,215出现的次数。

arr[1] 统计 16,777,216 ~ (16,777,216 + 16,777,215) 出现的次数。

……

arr[255] 统计 (2^32 - 1 - 16,777,215) ~ 2^32 - 1 出现过的数。

由于现在的数个数是 40 亿, 不到 2 的 32 次方,所以,肯定有某个位置上的arr值不够 16,777,216 个。 由于只需要找到一个没有出现过的数,所以只需要在不够 16,777,216 这个范围的位置上进行再一次的 256 份的划分,然后再次使用上述逻辑,直到划分到某个数单独作为一个范围同时没出现过,这个数就是我们需要找的数。

问题4

32位无符号整数的范围内有 4294967295 个数,现在有一个正好包含 40 亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数, 只能使用有限几个变量,如何找到一个没出现过的数(找到一个即可)。

参考问题3,我们可以设置一个变量 L 定位第 0 个数,设置变量 R 定位 2^32-1 上的数,设置 M 变量定位到中间位置。由于一共有 2^32 次方个数,所以统计左边和右边都应该有 2^31 个数,但是总共 40 亿个数,所以必然有一边不满足 2^31 方个数,然后不满足的这一边继续二分,重复上述逻辑,即可找到没有出现过的一个数字。

问题3 和问题4 类似,我们可以得到一个结论: 如果内存3KB,就用256分,如果是几个变量,就用二分。

问题5

有一个包含 100 亿个 URL 的大文件,假设每个 URL 占用 64B,请找出其中所有重复的 URL。

如果允许失误率,这个问题可以使用布隆过滤器来解决。

如果不允许失误,则可以使用问题1的方法,使用哈希函数结合取模操作,分到小文件再判断重复。

问题6

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

问题2中,拿一个 bit 来表示一个数出现过一次或者没出现过,本问题我们可以拿两个 bit 来表达一个数出现的次数。

00:表示没出现

01:表示出现过 1 次

10:表示出现过 2 次

11:表示出现过 3 次及 3 次以上。

经过以上处理,只需要看哪些是 10 状态的数。

问题7

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

参考问题3的做法,把整个 2^32 个数均分到一个 256 长度的 long 型数组中,每个位置管理 16,777,216 个数出现的次数。 然后逐个累加区间中数字出现的次数,一直累加到 21 亿左右,即可判断,中位数一定存在这个区间中。

问题8

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

主要思路

以 Java 为例,定义一个数据结构

class Node {
    long value;
    long times;    
}

value 表示文件中的数值,times 表示文件中的数值出现的次数。

然后设置一个大根堆存 Node 。value大的数值在堆顶, 假设堆大小我们设置为3,每次遍历的数和次数加入大根堆。大根堆满了以后,如果新加入一个大根堆中没有的数,则比较新加入的数和大根堆堆顶元素,如果比堆顶元素小,则剔除堆顶元素,加入新元素。遍历一轮以后,大根堆中的三个数一定是整个文件中最小的三个数。然后把这三个数和次数依次写入新文件,然后继续上述遍历和处理。每次都可以拿到三个排序后的数值,直接加入到新文件即可。

所以,因为本问题中有 5GB 内存,根据我们上述的方案,绰绰有余。

问题9

某搜索公司一天的用户搜索词汇是海量的(百亿数据量),请设计一种求出每天热门 Top100 词汇的可行办法。

参考问题1,我们可以使用哈希函数结合取模操作,分到小文件思想。

然后使用大根堆,统计每个文件中的 top100。

最后,把每个文件对应的大根堆的堆顶元素弹出放入新的一个大根堆(假设这个大根堆叫 superHeap )中。然后从这个新的大根堆的堆顶弹出堆顶元素,即为全局top1。

然后看这个弹出的堆顶元素是来自于哪个文件,继续把这个文件对应的大根堆堆顶元素弹出,继续放入 superHeap 中,然后从 superHeap 弹出堆顶元素,就是全局top2。

.....

依次类推,直到 top100。

资源限制技巧汇总

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

更多

算法和数据结构笔记

参考资料

算法和数据结构体系班-左程云

posted @ 2021-10-06 15:29  Grey Zeng  阅读(281)  评论(0编辑  收藏  举报