﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-阿二的梦想船-随笔分类-算法题目</title><link>http://www.cppblog.com/richbirdandy/category/8229.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 09 Sep 2008 19:22:22 GMT</lastBuildDate><pubDate>Tue, 09 Sep 2008 19:22:22 GMT</pubDate><ttl>60</ttl><item><title>从海量数据中找出中位数</title><link>http://www.cppblog.com/richbirdandy/archive/2008/09/09/61426.html</link><dc:creator>阿二</dc:creator><author>阿二</author><pubDate>Tue, 09 Sep 2008 14:49:00 GMT</pubDate><guid>http://www.cppblog.com/richbirdandy/archive/2008/09/09/61426.html</guid><wfw:comment>http://www.cppblog.com/richbirdandy/comments/61426.html</wfw:comment><comments>http://www.cppblog.com/richbirdandy/archive/2008/09/09/61426.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/richbirdandy/comments/commentRss/61426.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/richbirdandy/services/trackbacks/61426.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">题目和基本思路都来源网上，本人加以整理。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">题目：在一个文件中有 10G 个整数，乱序排列，要求找出中位数。内存限制为 2G。只写出思路即可（内存限制为 2G的意思就是，可以使用2G的空间来运行程序，而不考虑这台机器上的其他软件的占用内存）。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">关于中位数：数据排序后，位置在最中间的数值。即将数据分成两部分，一部分大于该数值，一部分小于该数值。中位数的位置：当样本数为奇数时，中位数=(N+1)/2 ; 当样本数为偶数时，中位数为N/2与1+N/2的均值（那么10G个数的中位数，就第5G大的数与第5G+1大的数的均值了）。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">分析：明显是一道工程性很强的题目，和一般的查找中位数的题目有几点不同。<br>1. 原数据不能读进内存，不然可以用快速选择，如果数的范围合适的话还可以考虑桶排序或者计数排序，但这里假设是32位整数，仍有4G种取值，需要一个16G大小的数组来计数。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">2. 若看成从N个数中找出第K大的数，如果K个数可以读进内存，可以利用最小或最大堆，但这里K=N/2,有5G个数，仍然不能读进内存。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">3. 接上，对于N个数和K个数都不能一次读进内存的情况，《编程之美》里给出一个方案：设k&lt;K,且k个数可以完全读进内存，那么先构建k个数的堆，先找出第0到k大的数，再扫描一遍数组找出第k+1到2k的数，再扫描直到找出第K个数。虽然每次时间大约是nlog(k)，但需要扫描ceil(K/k)次，这里要扫描5次。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">解法：首先假设是32位无符号整数。<br>1. 读一遍10G个整数，把整数映射到256M个区段中，用一个64位无符号整数给每个相应区段记数。<br>说明：整数范围是0 - 2^32 - 1，一共有4G种取值，映射到256M个区段，则每个区段有16（4G/256M = 16）种值，每16个值算一段， 0～15是第1段，16～31是第2段，&#8230;&#8230;2^32-16 ～2^32-1是第256M段。一个64位无符号整数最大值是0～8G-1，这里先不考虑溢出的情况。总共占用内存256M&#215;8B=2GB。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">2. 从前到后对每一段的计数累加，当累加的和超过5G时停止，找出这个区段（即累加停止时达到的区段，也是中位数所在的区段）的数值范围，设为[a，a+15]，同时记录累加到前一个区段的总数，设为m。然后，释放除这个区段占用的内存。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">3. 再读一遍10G个整数，把在[a，a+15]内的每个值计数，即有16个计数。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">4. 对新的计数依次累加，每次的和设为n，当m+n的值超过5G时停止，此时的这个计数所对应的数就是中位数。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">总结：<br>1.以上方法只要读两遍整数，对每个整数也只是常数时间的操作，总体来说是线性时间。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">2. 考虑其他情况。<br>若是有符号的整数，只需改变映射即可。若是64为整数，则增加每个区段的范围，那么在第二次读数时，要考虑更多的计数。若过某个计数溢出，那么可认定所在的区段或代表整数为所求，这里只需做好相应的处理。噢，忘了还要找第5G+1大的数了，相信有了以上的成果，找到这个数也不难了吧。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">3. 时空权衡。<br>花费256个区段也许只是恰好配合2GB的内存（其实也不是，呵呵）。可以增大区段范围，减少区段数目，节省一些内存，虽然增加第二部分的对单个数值的计数，但第一部分对每个区段的计数加快了（总体改变？？待测）。</p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">4. 映射时尽量用位操作，由于每个区段的起点都是2的整数幂，映射起来也很方便。<br></p>
<img src ="http://www.cppblog.com/richbirdandy/aggbug/61426.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/richbirdandy/" target="_blank">阿二</a> 2008-09-09 22:49 <a href="http://www.cppblog.com/richbirdandy/archive/2008/09/09/61426.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>