BitMap:一个文件中有20亿个数字,对这20亿个数字进行去重并按照从小到大排序。

本文详细探讨了如何利用BitMap算法解决实际问题,面对20亿个不重复的数字,通过位映射实现去重并排序,同时对比内存占用和传统方法的优劣。关键步骤包括拆分文件、哈希定位、堆排序和BitMap操作,最终达到高效存储与快速查询目的。
摘要由CSDN通过智能技术生成

 原文:《BitMap:一个文件中有20亿个数字,对这20亿个数字进行去重并按照从小到大排序。》

BitMap简介

BitMap是一种基于位的映射算法,它的基本思想就是用一个bit位,来记录某个元素的状态,而bit位所在的下标,通过该元素可以计算得到。

举个例子

假设有这样一个需求:一个文件中有20亿个随机正整数,对这20亿个数字进行去重并按照从小到大排序。20亿个数字大于0并小于2147483647,计算机的内存1G。

在Java中,int占4字节,1字节=8位(1 byte = 8 bit)。

如果每个数字用int存储,那就是20亿个int,因而占用的空间约为 (2000000000*4/1024/1024/1024)≈7.45G

答案一:

思路:20亿数字,分成100份,把每一份中的最小的数字取出来放到堆中,这样堆中就得到了100个数字,把堆进行从小到大排序,堆顶那个数字就是20亿中最小的那个。

  1. 把这个7.45GB的大文件,用哈希分成100个小文件,每个小文件平均74.5MB左右(理想情况),把20亿个数字对100取模,模出来的结果在0到99之间,每个结果对应一个文件,所以这里的哈希函数是 h = x % 100。
  2. 拆分完成得到一些几十MB的小文件之后这些小文件内存就装得下了,把每个小文件都进行排序,可以用快速排序,归并排序,堆排序等等。
  3. 100个小文件内部排好序之后,就要把这些内部有序的小文件,合并成一个大的文件,可以用二叉堆(二叉堆天生有序)来做100路合并的操作,每个小文件是一路,合并后的大文件仍然有序。
  • 首先遍历100个文件,每个文件里面取第一个数字(因为文件内已经排序过了,所以取到的一定是这个文件内最小的数字),组成 (数字, 文件号) 这样的组合加入到堆里(假设是从小到大排序,用小顶堆),遍历完后堆里有100个 (数字,文件号) 这样的元素。这样,堆里的100个数字是100个文件中的最小的数字,而因为堆(小顶堆)会自动排序,堆顶一定是100个数字中最小的那个数字,所以此时堆顶已经是20亿中最小的那个数字了。
  • 然后从堆顶拿元素出来,每拿出一个元素,把它的文件号读取出来,然后去对应的文件里取出一个数字(当前文件内最小的数字)进入堆(这样可以保证堆里不会漏掉某个文件,堆中的100个数字分别是每个文件的最小数字),拿出来的元素追加到最终结果的文件里,判断是否与文件内的前一个元素重复,重复则跳过,不重复则插入。
  • 按照上面的操作,直到堆被取空了,此时最终结果文件里的全部数字就是有序且去重复的了。

思考:有其他的解决方案吗?

答案二:

思路:既然内存下放不了20亿个数字,那20亿个bit放的下吧,我们用bit对数字进行标识该数字是否存在,1代表存在,0代表不存在。(如果要标识是否重复,可以再加一个bit,用两个bit来标识一个数字,思路是一样的,题目中是去重复,则此处我们只要标识是否存在即可,用一个bit即可满足)。

20亿个数用,每个数用1个bit来标识就是20亿bit,占用空间约为 (2000000000/8/1024/1024/1024)≈0.233G。

比如我们想要表示{2,9,3,1,6,14,8,12}则:

计算机中内存分配的最小单位是Byte(8bit),上面有一个长度为2Byte(16bit)的数组,我们用这个数组最多可以标识16个数字是否存在,要知道一个int数字就要占用4Byte,而我们仅仅用了2Byte,并且还是按顺序的!所以此时我们发现用bit标识的方式来标识数字是否存在的好处就是,节约存储空间而且天生有序!

我们从上图概括一些公式出来:

  • 数字所在的Byte=数字/8(比如:13/8=1,所以13在Byte(1))
  • 数字所在Byte中的位=数字%8(比如:13%8=5,所以13在Byte(1)中的第5位(从0位开始算),也就是1<<5)

这种运算我们怎样添加数字进去呢,比如想要把13添加进去?

首先我们上面计算过13应该位于Byte(1)下的第5位,所以此时我们就不用计算了,直接往里放:

所以总结下,假设待插入数为X,插入后的Byte则为:Byte(X/8)| 1<<(X%8);

那怎样把13移除掉呢?

理解了上面的计算,移除就简单了,添加的时候我们是1<<5之后进行的或运算,那么移除的时候把1<<5(00100000)进行取反,也就是~(1<<5)(11011111),然后与Byte进行&(与运算)就可以了。

所以公式就是:Byte(X/8) & (~(1<<(X%8)));

BitMap的作用:

  • 快速排序:前面可以看的出来,我们把文件中的数字遍历一遍计算放到数组后,就已经是顺序存放的了,遍历取就是已经排序后的结果了。所以时间复杂度是O(n)。
  • 快速去重:按上面的例子,假如出现了两个13,但是因为是|运算,第二次添加也不会有其他的操作,所以直接就去重了。时间复杂度是O(n)
  • 快速查找:比如说假设我们想知道13是否存在,则直接计算13所在的Byte和所在位,判断该位是0还是1,如果是1就是存在。所以查找效率是O(1)。

    优点:

            运算效率高,不需要进行比较和移位;

            占用内存少,比如N=10000000;只需占用内存为N/8=1250000Byte=1.25M

    缺点:

            所有的数据不能重复。即不可对重复的数据进行排序和查找。

            只有当数据比较密集时才有优势。

总结

Bitmap主要用于快速检索关键字状态,通常要求关键字是一个连续的序列(或者关键字是一个连续序列中的大部分), 最基本的情况,使用1bit表示一个关键字的状态(可标示两种状态),但根据需要也可以使用2bit(表示4种状态),3bit(表示8种状态)。

Bitmap的主要应用场合:表示连续(或接近连续,即大部分会出现)的关键字序列的状态(状态数/关键字个数 越小越好)。

32位机器上,对于一个整型数,比如int a=1 在内存中占32bit位,这是为了方便计算机的运算。但是对于某些应用场景而言,这属于一种巨大的浪费(比如假设给的数不是20亿个,是10个),因为我们可以用对应的32bit位对应存储十进制的0-31个数,而这就是Bit-map的基本思想。Bit-map算法利用这种思想处理大量数据的排序、查询以及去重。

原文:《BitMap:一个文件中有20亿个数字,对这20亿个数字进行去重并按照从小到大排序。》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值