zoukankan      html  css  js  c++  java
  • 【转】海量数据求中位数

    以下是转载的:个人感觉第四种方法很巧妙,O(N)复杂度最好也最快!第二种方法不太理解~

    腾讯一面问到了,用的算法导论中的Kth算法,期望时间复杂度为O(n)。后来想了想,万一数据多的来根本不能一次读入内存,这个时候该如何解决呢?

    题目如下:
    只有2G内存的pc机,在一个存有10G个整数的文件,从中找到中位数,写一个算法。

    给出了四种方法来解决

    算法:

    1.利用外排序的方法,进行排序 ,然后再去找中位数
    注释:外部排序基本上由两个相对独立的阶段组成。首先,按可用内存大小,将外存上含n个记录的文件分成若干长度为h的子文件,依次读入内存并利用有效的内部排序方法对它们进行排序,并将排序后得到的有序子文件重新写入外存,通常称这些有序子文件为归并段或顺串;然后,对这些归并段进行逐趟归并,使归并段(有序的子文件)逐渐由小到大,直至得到整个有序文件为止。
     
    2.另外还有个思路利用堆
    先求第1G大,然后利用该元素求第2G大,然后利用第2G大,求第3G大...当然这样的话虽不需排序,但是磁盘操作会比较多,具体还需要分析下与外排序的效率哪个的磁盘IO会比较多
    建立一个1g个整数的最大值堆,如果元素小于最大值则入堆,这样可以得到第1g大的那个元素然后利用这个元素,重新建一次堆,这次入堆的条件还要加上大于这个第1g大的元素,这样建完堆可以得到第2g大的那个...
     
    3.借鉴基数排序思想
    偶认为可以用位来判断计数,从最高位到最低位,为了方便表述我们假设为无符号整数,即0x00000000~0xFFFFFFFF依次递增,那么可以遍历所有数据,并记录最高位为0和1的个数(最高位为0的肯定是小于最高位为1的)记为N0、N1
    那么根据N0和N1的大小就可以知道中位数的最高位是0还是1
    假设N0>N1,那么再计算N00和N01,
    如果N00>(N01+N1),则说明中位数的最高两位是00
    再计算N000和N001.。。。依次计算就能找到中位数
     
    如果改进一下,设定多个计数器
    好像一次磁盘io也可以统计出N0,N00,....的数值
     
    4.借鉴桶排序思想
    一个整数假设是32位无符号数
    第一次扫描把0~2^32-1分成2^16个区间,记录每个区间的整数数目
    找出中位数具体所在区间65536*i~65536*(i+1)-1
    第二次扫描则可找出具体中位数数值


    第一次扫描已经找出中位数具体所在区间65536*i~65536*(i+1)-1

    然后第二次扫描再统计在该区间内每个数出现的次数,就可以了

    题目:在一个文件中有 10G 个整数,乱序排列,要求找出中位数。内存限制为 2G。只写出思路即可(内存限制为 2G的意思就是,可以使用2G的空间来运行程序,而不考虑这台机器上的其他软件的占用内存)。
    分析:既然要找中位数,很简单就是排序的想法。那么基于字节的桶排序是一个可行的方法
    思想:将整形的每1byte作为一个关键字,也就是说一个整形可以拆成4个keys,而且最高位的keys越大,整数越大。如果高位keys相同,则比较次高位的keys。整个比较过程类似于字符串的字典序。
    第一步:把10G整数每2G读入一次内存,然后一次遍历这536,870,912个数据。每个数据用位运算">>"取出最高8位(31-24)。这8bits(0-255)最多表示255个桶,那么可以根据8bit的值来确定丢入第几个桶。最后把每个桶写入一个磁盘文件中,同时在内存中统计每个桶内数据的数量,自然这个数量只需要255个整形空间即可。
    代价:(1) 10G数据依次读入内存的IO代价(这个是无法避免的,CPU不能直接在磁盘上运算)。(2)在内存中遍历536,870,912个数据,这是一个O(n)的线性时间复杂度。(3)把255个桶写会到255个磁盘文件空间中,这个代价是额外的,也就是多付出一倍的10G数据转移的时间。
    第二步:根据内存中255个桶内的数量,计算中位数在第几个桶中。很显然,2,684,354,560个数中位数是第1,342,177,280个。假设前127个桶的数据量相加,发现少于1,342,177,280,把第128个桶数据量加上,大于1,342,177,280。说明,中位数必在磁盘的第128个桶中。而且在这个桶的第1,342,177,280-N(0-127)个数位上。N(0-127)表示前127个桶的数据量之和。然后把第128个文件中的整数读入内存。(平均而言,每个文件的大小估计在10G/128=80M左右,当然也不一定,但是超过2G的可能性很小)。
    代价:(1)循环计算255个桶中的数据量累加,需要O(M)的代价,其中m<255。(2)读入一个大概80M左右文件大小的IO代价。
    注意,变态的情况下,这个需要读入的第128号文件仍然大于2G,那么整个读入仍然可以按照第一步分批来进行读取。
    第三步:继续以内存中的整数的次高8bit进行桶排序(23-16)。过程和第一步相同,也是255个桶。
    第四步:一直下去,直到最低字节(7-0bit)的桶排序结束。我相信这个时候完全可以在内存中使用一次快排就可以了。
    整个过程的时间复杂度在O(n)的线性级别上(没有任何循环嵌套)。但主要时间消耗在第一步的第二次内存-磁盘数据交换上,即10G数据分255个文件写回磁盘上。一般而言,如果第二步过后,内存可以容纳下存在中位数的某一个文件的话,直接快排就可以了。关于快排的效率,可以看看我博客中的数据

  • 相关阅读:
    [大山中学模拟赛] 2016.9.17
    [DP优化方法之斜率DP]
    Gengxin讲STL系列——String
    小班讲课之动态规划基础背包问题
    ubuntu安装体验
    小班出题之字符串基础检测
    G
    B
    小项目--反eclass
    树--天平问题
  • 原文地址:https://www.cnblogs.com/-sunshine/p/3201920.html
Copyright © 2011-2022 走看看