您的位置:程序门 -> 专题开发/技术/项目 -> 数据结构与算法



从10亿条整数中,选择出现次数最多的100个整数


[收藏此页] [打印本页]选择字色:背景色:字体:[][][]


从10亿条整数中,选择出现次数最多的100个整数[无满意答案结贴,结贴人:wuliming_sc]
发表于:2007-10-14 15:23:09 楼主
问题的具体描述:
假设有10亿个整数,这里面的整数有不少重复的,要求选取重复次数最多的100个整数  
(也就是选取出现频率最高的100个整数)  
发表于:2007-10-14 19:48:111楼 得分:0
这个问题关键的是整数的取值范围。
发表于:2007-10-14 21:57:362楼 得分:0
请lz明确

1.   整数的取值范围   2^32   还是   2^64
2.   重复的数字大概的比例(这样选择的做法就不同,如果绝大多数是重复的,用hash就可以简单解决了)
3.   明确内存限制

发表于:2007-10-15 09:17:513楼 得分:0
假设取直范围是2^32,内存1gb。,怎么利用hash解决啊?
发表于:2007-10-15 10:09:114楼 得分:0
假设整数以32bit存储
发表于:2007-10-15 10:11:505楼 得分:0
再假设内存无明确的限制,但是尽量用最有和最高效的办法解决问题
发表于:2007-10-15 10:15:296楼 得分:0
假设元素是非负数,且取值范围是[0,2^32),可以利用hash分段处理。

定义一个hash表:unsigned   int   hash[len](len=512*1024*1024/4=134217728),这样,每一段需要分配512m内存,每一次处理了134217728个数据,这样,需要处理8次(10^9/134217728=7.45)。
定义一个结构体:
struct   s{
        unsigned   int   freq;//频率
          unsigned   int   num;//数据
};
定义一个类型为结构题s的大小为800的数组s   list[800]。

处理第一段,也就是处理大小在[0,134217728)范围内的数。
将hash表所有单元清零。
扫描一遍原数据。设扫描到数据i,若i在[0,134217728)范围内,++hash[i]。
扫描完后,从hash表中找出值最大的100个值(也就是在第一段中出现次数最多的元素),这个可以用第k大元素查找算法。将这100值,插入list中(freq就是hash表中的值,num可以由段值和偏移值确定)。

处理第二段,也就是处理大小在[134217728,134217728*2)范围内的数。
还是用处理第一段时用的hash表,只是先将它所有单元清零。
扫描一遍原数据。设扫描到数据i,若i在[134217728,134217728*2)范围内,++hash[i-134217728]。
扫描完后,从hash表中找出值最大的100个值(也就是在第二段中出现次数最多的元素),这个可以用第k大元素查找算法。将这100值,插入list中(freq就是hash表中的值,num可以由段值和偏移值确定)。

依照上面的步骤,处理完全部8段。

这样,list[]中就有了800个元素,对分量freq,用第k大元素查找算法,找到这800个元素中的最大的100个值,那么,这100个元素中的分量num,就组成了原数据中出现次数最多的前100个整数。

第k大元素查找算法的选取:
若用基于快速排序思想的第k大元素查找算法,其平均时间复杂度虽然很好,但最坏情况下却可达到o(n^2)的时间复杂度。
可以考虑专门针对无符号整数的第k大元素查找算法:基于位查找的第k大元素查找算法。

大致过程如下:
元素有32为二进位,分为4段。
先查找每个元素的二进制的前8位的分布,初步确定第k大元素的前8位的形式,再对那一段的数据的次8位的进行查找,确定第k大元素的次8位的形式,...
这样,进行了4次查找后,就可以定位第k位数据。

发表于:2007-10-15 10:22:477楼 得分:0
如果内存无限制的话,直接分配4*2^32=16g的内存,直接hash映射,然后用基于位查找的第k大元素查找算法,找到前100位的数据就可以。

如果16g太大,也可以降低为8g、4g、2g、1g等等,按照上面的方法分段处理就可以了。
发表于:2007-10-15 10:32:298楼 得分:0
搞错了,分配512m内存的话,应该是要处理32次.
发表于:2007-10-15 11:26:569楼 得分:0
to   medie2005

你给的算法是不是基于原数是有序的呢?  
如果无序的话   很可能第二段的数据有   [0,134217728)的数   这样算法就会错误把    

请指点
发表于:2007-10-15 11:32:0710楼 得分:0
[size=24px]嗯,通过楼上的办法可以解决该问题,思路也比较清楚。
是否还有更优更高效的解决办法呢?[/
size]
发表于:2007-10-15 11:45:5311楼 得分:0
to   skyspark   :

可能是我没描述清楚,再说一下。
我所谓的“分段”,是针对原来数据中的元素大小来分段。比如:原来数据是7,2,5,9,2,5,8,0,8,1
范围是[0,10),若分2段,我的方法是先处理大小在区间[0,5)内的数据,再处理大小在区间[5,10)内的数据。
由于这两段的范围大小是一样的,只是起始点不同,因此,可以同用一个hash表,只需要改动一下hash函数就可以了。
发表于:2007-10-15 11:50:5212楼 得分:0
to   medie2005

那我是否可以理解为   第一次扫描所有的原数剧   但是仅处理落在第一段的值  

第二次再扫描所有的原数剧   这时处理落在第二段的值?
发表于:2007-10-15 14:05:4413楼 得分:0
ls对。
发表于:2007-10-15 15:41:3014楼 得分:0
先排序,再来数个数。
那么多数肯定是存在磁盘上的吧,排序的时候也要利用磁盘。
先分范围保存到不同的文件,再每个文件各自排序,最后合并保存到一个文件。
10亿个数就是1g,   如果是32位数(4字节),   总共大小就是4g。如果计划占用内存512m,理想的情况就分8个范围。
因楼主并没有说明数值的分布情况,所以要考虑某些极端的情况,比如这10亿个数,全部相同,或绝大部分是同一个数数值。实现起来还是有点麻烦
发表于:2007-10-15 16:01:2515楼 得分:0
分段统计是合理的。还有考虑到数据量很大,在硬盘允许的情况下先按值的范围划分归到不同的文件中,然后在对已划分的文件逐个统计会好一点。毕竟   读2*10亿个+写1*10亿个   应该快于   读32*10亿个   吧。
发表于:2007-10-15 16:58:5416楼 得分:0
tiger_zhao   说的对。
时间太仓促,忽略了这个问题,谢谢指教。

另:list[]的大小可以直接设置为100,第一段扫描完后,将list[]按分量freq的大小建成最小堆。
不过,3200个单元的unsigned   int与512m比起来,实在算不上什么。
发表于:2007-10-15 23:05:2717楼 得分:0
我觉得楼主这题跟之发的那个“从2.5y找不重复数字”那题差不多。

按那贴27楼的算法可以这样:

    byte   marks[2^29];               //512m   *   8bit   =   4g   bit
    byte   remarks[2^27];         //128m   *   8bit   >   10亿
    用marks表示数字是否出现,用remarks表示数字是否重复出现。

   
a:   第一次扫描,将数据读出,置marks标志,如果marks已置位,则再置remarks,表示重复。
b:   从remarks检索出重复的数字,进行统计,扫描出有count个数字是重复的。
c:   free(marks)
d:  
    typedef   struct   counter   {  
        int   num;  
        dword   count;  
    };
    第二次扫描,分配m_counters   =   new   counter[count],根据remarks标志了重复了数字,置相应数字计数(可以用avl树之类数据结构处理)。

e:找出m_counters中count前100的数字。

大概这样,好像有点麻烦      
发表于:2007-10-16 16:47:5318楼 得分:0
参考这个题目,据说是微软出的:
有一个整数数组,请求出两两之差绝对值最小的值,记住,只要得出最小值即可,不需要求出是哪两个数。

  研究了好几天,写出来一个看起来象o(n)的算法,o(nlog)就不用写了.  
c# code
using system; namespace minabs { class program { static void main(string[] args) { int n = 100; int[] a = new int[n]; random rand = new random(); int min = int.maxvalue; int max = int.minvalue; console.writeline("产生了" + n + "个数据的实验数组,"); forint i = 0; i < n; i++) { //赋值并且取到最大最小值 //a[i] = rand.next(int.minvalue, int.maxvalue); a[i] = rand.next(-100, 100); if (a[i] < min) { min = a[i]; } if (a[i] > max) { max = a[i]; } console.write(a[i] + " "); } console.writeline(); console.writeline("在o(n)内得到最大最小分别是:"); console.writeline(max + "" + min); long offset =long)max + math.abs((long)min); //规划数组的长度。每个byte有8位长 int len =int)(offset >> 3) +1 ; byte[] b = new byte[len]; int kkkk = 0; bool issame = false;//是否有重合点标记 //o(n)的时间内分配到了byte[]中。 forint i = 0; i < n; i++) { offset =long)a[i] -long)min; int index =int)(offset >> 3); int temp = b[index]; //把末k位变成1 //把右数第k位变成1 例如:(00101001->00101101,k=3) x | (1 << (k-1)) int tempoffset =1 << ( (int)(offset & 7) ) ); //判断重合 if!issame) { kkkk = temp & tempoffset; if ((temp & tempoffset) >= 1) { issame = true; //如果0算最小距离就在这里退出吧。本代码判断重合,但没把0作为结果。 } } int bbb = b[index]; b[index] |= (byte)(tempoffset); int aaa = b[index]; } //最小距离初始为最大。 console.writeline("在o(n)的时间内分配到了byte[]中,正在计算最小距离,请稍候。。。。。"); long minabs = long.maxvalue; long lastindex = -1; //在常数范围内循环,复杂度不增加。最坏的情况是32*int.maxvalue次。 forint i = 0; i < b.length; i++) { //if (b[i] == 0) { continue; } //在常数范围内循环,复杂度不增加。 forint k = 0; k < 8; k++) { if (((b[i] >> k) & 1) == 1) { if (lastindex >= 0) { long temp = ((long)i << 3) + k - lastindex; if (temp < minabs) { minabs = temp; console.writeline("目前得到了最小距离:" + minabs); } } lastindex = (i << 3) + k; } } } if (issame) { console.writeline("有重合点"); } else { console.writeline("无重合点"); } console.writeline("不考虑重合最小距离是:" + minabs); console.writeline("总复杂度是:o(n)"); console.readline(); } } }


快速检索

最新资讯
热门点击