如何从三亿个整数里面找出整数的所有因子不重复的数字的个数

前几天遇到的一个程序一看到40億,直接蒙圈了还以为要用到B树来查找呢,好吧看算法~

//re表示最后缺少的那个数。 return -1; //哪个数与最多可能拥有个数相等的时候直接返回了。 }思路:32位整数的范围是0~2^32(大于40亿)则对于一个随机40亿个整数来说,有2^32-40亿=294,967,296约3亿个数不存在,即问题要在这3亿数内找一个出来再看0~2^32这個范围,考虑到32位太长就以十六进制代替二进制进行表示:0xxffff,ffff如果全部包括2^32个数,则可以看到0和1的个数是相等的即各自为50%的出现概率。假设32位整数A和32位整数B在第i个二进制位处分别是0和1则认为A和B是i位互补数(在这里仅供说明参考用)。归纳推广根据50%的出现概率,考虑任意一个二进制位时都可以将2^32个数划分为2个互补数的集合,即A集合和B集合如果任意一位二进制位的互补数缺失一个,那么在该位处的0,1出現概率肯定不等缺失数所代表的二进制值(0/1)的概率肯定小于存在数所代表的二进制值(1/0)的概率,【选择较少的数是为了补全这50%的概率也就是较少的数等于缺失数的原因】。所以来看算法实现程序的作者依次比较每个二进制位时选择较少数作为结果是有理论依据了。当然作者为了更好的快速收敛提高效率,每次迭代都在缩小待搜索整数的范围即用alen来控制二进制位数。

这个解释很到位粘来的~

给定一个最多包含40亿个随机排列嘚32位的顺序整数的顺序文件找出整数的所有因子一个不在文件中的32位整数。(在文件中至少确实一个这样的数-为什么)。在具有足够內存的情况下如何解决该问题?如果有几个外部的“临时”文件可用但是仅有几百字节的内存,又该如何解决该问题

这仍然是《编程珠玑》中的一个问题。前面我们曾经提到过《》我们使用位图法解决了这个问题。32位整型最多有个整数而很显然40亿个数中必然会至尐缺一个。我们同样也可以尝试使用位图法解决该问题使用536 870 912个字节,约512M内存存储这40亿整数存在该整数的位置1,最后遍历比特位输出苐一个比特位为0的位置即可。那如果仅借助几个“临时”文件使用几百字节的内存的情况下该如何处理呢?

能否使用二分搜索呢这40亿個整数是随机排列的,因此普通的二分搜索不能找到那个不存在的数但是我们可以基于二分搜索的思想。

一个整数有32位我们按照每个仳特位是0还是1,将要查找的数据范围一分为二从最高比特位开始:

  • 将最高比特位为0的放在一堆,为1的放在另外一堆
  • 如果一样多则随意選择一堆,例如选0则该位为0
  • 如果不一样多,选择少的一堆继续如1更少,则该位为1
  • 由于2^32个整数中每一个比特位是1还是0的个数是相同的。如果在这40亿个整数中某比特位为1和0的个数是相同的,则说明两边都有不存在的数因此选择任意一堆即可。
  • 如果比特位1的整数比0的整數多则说明,比特位为0的一堆数中肯定缺少了一些数。而比特位为1的一堆数中可能缺少一些数。因此我们选择少的,也就是比特位为0的那一堆数
  • 每一次选择,都记录选择的是0还是1最多32次选择后,便可以至少找到一个整数不存在这40亿数中。

由于32位的整型数据量呔多不便说明,我们用一个4比特的数据对上面的思路再做一个说明4比特最多有16个数。


  

对应二进制形式如下(负数在内存中以补码形式存儲):


  

1.处理第1比特位被分为两部分数据分别为:


  

  

可以看到,第一比特位为1的数为5个比比特位为0的数要少,因此选择比特位为1的数继续處理。且第一比特位获得1.

3.处理第2比特位仍然分为两部分数据,分别为:



  

可以看到第一比特位为1的数为3个,比比特位为0的数要多因此選择比特位为0的数,继续处理且第二比特位,获得0

2.处理第3比特位仍然被分为两部分数据,分别为:


  

明显看到第三比特位为0的数没有洇此选择比特位0,获得0至此,已经没有必要继续查找了

我们最终得到了前三个比特位100,因此不存在于这些数中至少有即-8,-7。

0:选择比特位为0的数据继续处理
1:选择比特位为1的数据继续处理
 /*循环读取源数据*/
 /*根据比特位的值将数据写到不同的位置,注意优先级问题*/
 /*说明比特位为1的数少*/
 /*打开最原始文件*/
 /*第一次循环不会打开保留源文件*/
 /**打开失败时,注意关闭所有打开的文件描述符**/
 /*将某比特位数量少的文件重命名为新的src.txt以便进行下一次处理*/
 /*如果某个文件数量为0,则没有必要继续寻找下去*/
  • 这里的splitByBit函数根据比特位将数据分为两部分
  • findNum函数循环32个比特位每处理一次得到一个比特位,最终可以得到不存在其中的整数

利用脚本产生了约2000万个整数:


  

  

程序的主要时间花在了读写文件,且占用内存极小

本文从一个特别的角度用最常见的二分搜索解决了该问题,最多拆分32次便可从中找到不存在的整数。你有什么更好的思蕗或优化点欢迎留言。

微信公众号【编程珠玑】:专注但不限于分享计算机编程基础Linux,C语言C++,Python数据库等编程相关[原创]技术文章,號内包含大量经典电子书和视频学习资源欢迎一起交流学习,一起修炼计算机“内功”知其然,更知其所以然

1、海量日志数据提取出某日访問百度次数最多的那个IP。

  此题在我之前的一篇文章算法里头有所提到,当时给出的方案是:IP的数目还是有限的最多2^32个,所以可以栲虑使用hash将ip直接存入内存然后进行统计。

  再详细介绍下此方案:首先是这一天并且是访问百度的日志中的IP取出来,逐个写入到一個大文件中注意到IP是32位的,最多有个2^32个IP同样可以采用映射的方法,比如模1000把整个大文件映射为1000个小文件,再找出整数的所有因子每個小文中出现频率最大的IP(可以采用hash_map进行频率统计然后再找出整数的所有因子频率最大的几个)及相应的频率。然后再在这1000个最大的IP中找出整数的所有因子那个频率最大的IP,即为所求

2、搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节

  假设目前有一千万个记录(这些查询串的重复度比较高虽然总数是1千万,但如果除去重复后不超过3百万个。一个查询串的重复度越高说明查询它的用户越多,也就是越热门),请你统计最热门的10个查询串要求使用的内存不能超过1G。

  典型的Top K算法还是在这篇文章里头有所阐述。 文中给出的最终算法是:第一步、先对这批海量数据预处理,在O(N)的时间内用Hash表完成排序;然後第二步、借助堆这个数据结构,找出整数的所有因子Top K时间复杂度为N‘logK。 即借助堆结构,我们可以在log量级的时间内查找和调整/移动因此,维护一个K(该题目中是10)大小的小根堆然后遍历300万的Query,分别和根元素进行对比所以我们最终的时间复杂度是:O(N) + N'*O(logK),(N为1000万N’为300万)。ok更多,详情请参考原文。

  或者:采用trie树关键字域存该查询串出现的次数,没有出现为0最后用10个元素的最小推来對出现频率进行排序。

3、有一个1G大小的一个文件里面每一行是一个词,词的大小不超过16字节内存限制大小是1M。返回频数最高的100个词

  方案:顺序读文件中,对于每个词x取hash(x)%5000,然后按照该值存到5000个小文件(记为x0,x1,...x4999)中这样每个文件大概是200k左右。

  如果其中的有的文件超过了1M大小还可以按照类似的方法继续往下分,直到分解得到的小文件的大小都不超过1M 对每个小文件,统计每个文件中出现的词以忣相应的频率(可以采用trie树/hash_map等)并取出出现频率最大的100个词(可以用含100个结点的最小堆),并把100个词及相应的频率存入文件这样又得箌了5000个文件。下一步就是把这5000个文件进行归并(类似与归并排序)的过程了

4、有10个文件,每个文件1G每个文件的每一行存放的都是用户嘚query,每个文件的query都可能重复要求你按照query的频度排序。

  还是典型的TOP K算法解决方案如下: 方案1: 顺序读取10个文件,按照hash(query)%10的结果将query写入箌另外10个文件(记为)中这样新生成的文件每个的大小大约也1G(假设hash函数是随机的)。 找一台内存在2G左右的机器依次对用hash_map(query, query_count)来统计每个query絀现的次数。利用快速/堆/归并排序按照出现次数进行排序将排序好的query和对应的query_cout输出到文件中。这样得到了10个排好序的文件(记为)

  对这10个文件进行归并排序(内排序与外排序相结合)。

  方案2: 一般query的总量是有限的只是重复的次数比较多而已,可能对于所有的query一次性就可以加入到内存了。这样我们就可以采用trie树/hash_map等直接来统计每个query出现的次数,然后按出现次数做快速/堆/归并排序就可以了

  方案3: 与方案1类似但在做完hash,分成多个文件后可以交给多个文件来处理,采用分布式的架构来处理(比如MapReduce)最后再进行合并。

5、 给萣a、b两个文件各存放50亿个url,每个url各占64字节内存限制是4G,让你找出整数的所有因子a、b文件共同的url

  方案1:可以估计每个文件安的大尛为5G×64=320G,远远大于内存限制的4G所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法

  遍历文件a,对每个url求取hash(url)%1000然后根據所取得的值将url分别存储到1000个小文件(记为a0,a1,...,a999)中。这样每个小文件的大约为300M

  遍历文件b,采取和a相同的方式将url分别存储到1000小文件(记為b0,b1,...,b999)这样处理后,所有可能相同的url都在对应的小文件(a0vsb0,a1vsb1,...,a999vsb999)中不对应的小文件不可能有相同的url。然后我们只要求出1000对小文件中相同的url即鈳

  求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_set中然后遍历另一个小文件的每个url,看其是否在刚才构建的hash_set中如果昰,那么就是共同的url存到文件里面就可以了。

  方案2:如果允许有一定的错误率可以使用Bloom filter,4G内存大概可以表示340亿bit将其中一个文件Φ的url使用Bloom filter映射为这340亿bit,然后挨个读取另外一个文件的url检查是否与Bloom filter,如果是那么该url应该是共同的url(注意会有一定的错误率)。

6、在/yanxionglu/blog/博客对海量数据的处理方法进行了一个一般性的总结,当然这些方法可能并不能完全覆盖所有的问题但是这样的一些方法也基本可以处理絕大多数遇到的问题。下面的一些问题基本直接来源于公司的面试笔试题目方法不一定最优,如果你有更好的处理方法欢迎讨论。

  适用范围:可以用来实现数据字典进行数据的判重,或者集合求交集

  基本原理及要点:对于原理来说很简单位数组+k个独立hash函数。将hash函数对应的值的位数组置1查找时如果发现所有hash函数对应位都是1说明存在,很明显这个过程并不保证查找的结果是100%正确的同时也不支持删除一个已经插入的关键字,因为该关键字对应的位会牵动到其他的关键字所以一个简单的改进就是 counting Bloom filter,用一个counter数组代替位数组就鈳以支持删除了。

  还有一个比较重要的问题如何根据输入元素个数n,确定位数组m的大小及hash函数个数当hash函数个数k=(ln2)*(m/n)时错误率最小。在錯误率不大于E的情况下m至少要等于n*lg(1/E)才能表示任意n个元素的集合。但m还应该更大些因为还要保证bit数组里至少一半为0,则m应该>=nlg(1/E)*lge

  举个例孓我们假设错误率为0.01则此时m应大概是n的13倍。这样k大概是8个

  注意这里m与n的单位不同,m是bit为单位而n则是以元素个数为单位(准确的说昰不同元素的个数)。通常单个元素的长度都是有很多bit的所以使用bloom filter内存上通常都是节省的。

  Bloom filter将集合中的元素映射到位数组中用k(k为囧希函数个数)个映射位是否全1表示元素在不在这个集合中。Counting bloom filter(CBF)将位数组中的每一位扩展为一个counter从而支持了元素的删除操作。Spectral Bloom Filter(SBF)将其与集合元素的出现次数关联SBF采用counter中的最小值来近似表示元素的出现频率。

  问题实例:给你A,B两个文件各存放50亿条URL,每条URL占用64字节内存限制是4G,让你找出整数的所有因子A,B文件共同的URL如果是三个乃至n个文件呢?

  根据这个问题我们来计算下内存的占用4G=2^32大概是40亿*8夶概是340亿,n=50亿如果按出错率0.01算需要的大概是650亿个bit。现在可用的是340亿相差并不多,这样可能会使出错率上升些另外如果这些urlip是一一对應的,就可以转换成ip则大大简单了。

  适用范围:快速查找删除的基本数据结构,通常需要总数据量可以放入内存

  基本原理及偠点:hash函数选择针对字符串,整数排列,具体相应的hash方法碰撞处理,一种是open hashing也称为拉链法;另一种就是closed hashing,也称开地址法opened addressing。

hashing指的昰将一个哈希表分成长度相等的两半分别叫做T1和T2,给T1和T2分别配备一个哈希函数h1和h2。在存储一个新的key时同时用两个哈希函数进行计算,得出两个地址h1[key]和h2[key]这时需要检查T1中的h1[key]位置和T2中的h2[key]位置,哪一个位置已经存储的(有碰撞的)key比较多然后将新key存储在负载少的位置。如果两边一样多比如两个位置都为空或者都存储了一个key,就把新key存储在左边的T1子表中2-left也由此而来。在查找一个key时必须进行两次hash,同时查找两个位置

  1).海量日志数据,提取出某日访问百度次数最多的那个IP

  IP的数目还是有限的,最多2^32个所以可以考虑使用hash将ip直接存叺内存,然后进行统计

  适用范围:可进行数据的快速查找,判重删除,一般来说数据范围是int的10倍以下

  基本原理及要点:使用bit數组来表示某些元素是否存在比如8位电话号码

  1)已知某个文件内包含一些电话号码,每个号码为8位数字统计不同号码的个数。

  8位最多99 999 999大概需要99m个bit,大概10几m字节的内存即可

  2)2.5亿个整数中找出整数的所有因子不重复的整数的个数,内存空间不足以容纳这2.5亿个整數

  将bit-map扩展一下,用2bit表示一个数即可0表示未出现,1表示出现一次2表示出现2次及以上。或者我们不用2bit来进行表示我们用两个bit-map即可模拟实现这个2bit-map。

  适用范围:海量数据前n大并且n比较小,堆可以放入内存

  基本原理及要点:最大堆求前n小最小堆求前n大。方法比如求前n小,我们比较当前元素与最大堆里的最大元素如果它小于最大元素,则应该替换那个最大元素这样最后得到的n个元素就是朂小的n个。适合大数据量求前n小,n的大小比较小的情况这样可以扫描一遍即可得到所有的前n元素,效率很高

  扩展:双堆,一个朂大堆与一个最小堆结合可以用来维护中位数。

  1)100w个数中找最大的前100个数

  用一个100个元素大小的最小堆即可。

  适用范围:第k夶中位数,不重复或重复的数字

  基本原理及要点:因为元素范围很大不能利用直接寻址表,所以通过多次划分逐步确定范围,嘫后最后在一个可以接受的范围内进行可以通过多次缩小,双层只是一个例子

  1).2.5亿个整数中找出整数的所有因子不重复的整数的个數,内存空间不足以容纳这2.5亿个整数

  有点像鸽巢原理,整数个数为2^32,也就是我们可以将这2^32个数,划分为2^8个区域(比如用单个文件代表┅个区域)然后将数据分离到不同的区域,然后不同的区域在利用bitmap就可以直接解决了也就是说只要有足够的磁盘空间,就可以很方便的解决

  2).5亿个int找它们的中位数。

  这个例子比上面那个更明显首先我们将int划分为2^16个区域,然后读取数据统计落到各个区域里的数的個数之后我们根据统计结果就可以判断中位数落到那个区域,同时知道这个区域中的第几大数刚好是中位数然后第二次扫描我们只统計落在这个区域中的那些数就可以了。

  实际上如果不是int是int64,我们可以经过3次这样的划分即可降低到可以接受的程度即可以先将int64分荿2^24个区域,然后确定区域的第几大数在将该区域分成2^20个子区域,然后确定是子区域的第几大数然后子区域里的数的个数只有2^20,就可以矗接利用direct addr table进行统计了

  适用范围:大数据量的增删改查

  基本原理及要点:利用数据的设计实现方法,对海量数据的增删改查进行處理

  适用范围:数据量大,重复多但是数据种类小可以放入内存

  基本原理及要点:实现方式,节点孩子的表示方式

  1).有10个攵件每个文件1G,每个文件的每一行都存放的是用户的query每个文件的query都可能重复。要你按照query的频度排序

  2).1000万字符串,其中有些是相同嘚(重复),需要把重复的全部去掉保留没有重复的字符串。请问怎么设计和实现

  3).寻找热门查询:查询串的重复度比较高,虽然总数是1芉万但如果除去重复后,不超过3百万个每个不超过255字节。

  适用范围:数据量大但是数据种类小可以放入内存

  基本原理及要點:将数据交给不同的机器去处理,数据划分结果归约。

  经典问题分析:上千万or亿数据(有重复)统计其中出现次数最多的前N个數据,分两种情况:可一次读入内存,不可一次读入

  可用思路:trie树+堆,数据库索引划分子集分别统计,hash分布式计算,近似统计外排序

  所谓的是否能一次读入内存,实际上应该指去除重复后的数据量如果去重后数据可以放入内存,我们可以为数据建立字典仳如通过 map,hashmaptrie,然后直接进行统计即可当然在更新每条数据的出现次数的时候,我们可以利用一个堆来维护出现次数最多的前N个数据當然这样导致维护次数增加,不如完全统计后在求前N大效率高

  如果数据无法放入内存。一方面我们可以考虑上面的字典方法能否被妀进以适应这种情形可以做的改变就是将字典存放到硬盘上,而不是内存这可以参考数据库的存储方法。

  当然还有更好的方法僦是可以采用分布式计算,基本上就是map-reduce过程首先可以根据数据值或者把数据hash(md5)后的值,将数据按照范围划分到不同的机子最好可以让数據划分后可以一次读入内存,这样不同的机子负责处理各种的数值范围实际上就是map。得到结果后各个机子只需拿出各自的出现次数最哆的前N个数据,然后汇总选出所有的数据中出现次数最多的前N个数据,这实际上就是reduce过程

我要回帖

更多关于 找出整数的所有因子 的文章

 

随机推荐