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

wuliming_sc 2007-10-14 03:23:09
问题的具体描述:
假设有10亿个整数,这里面的整数有不少重复的,要求选取重复次数最多的100个整数
(也就是选取出现频率最高的100个整数)
...全文
1753 18 打赏 收藏 转发到动态 举报
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
林学森 2011-03-23
  • 打赏
  • 举报
回复
有个问题,得到的List[800]取前100,并不能代表原来这10亿个的前100吧。
比如第一组中的前100以后的,并不见得比其它组中100以内的频率低啊。
我的理解有问题吗?

[Quote=引用 6 楼 medie2005 的回复:]
假设元素是非负数,且取值范围是[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{
uns……
[/Quote]
超级大笨狼 2007-10-16
  • 打赏
  • 举报
回复
参考这个题目,据说是微软出的:
有一个整数数组,请求出两两之差绝对值最小的值,记住,只要得出最小值即可,不需要求出是哪两个数。

研究了好几天,写出来一个看起来象O(n)的算法,O(nlog)就不用写了.
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 + "个数据的实验数组,");
for (int 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[]中。

for (int 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次。

for (int i = 0; i < B.Length; i++)
{
//if (B[i] == 0) { continue; }
//在常数范围内循环,复杂度不增加。
for (int 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();

}


}
}

skyspark 2007-10-15
  • 打赏
  • 举报
回复
to medie2005

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

第二次再扫描所有的原数剧 这时处理落在第二段的值?
medie2005 2007-10-15
  • 打赏
  • 举报
回复
to skyspark :

可能是我没描述清楚,再说一下。
我所谓的“分段”,是针对原来数据中的元素大小来分段。比如:原来数据是7,2,5,9,2,5,8,0,8,1
范围是[0,10),若分2段,我的方法是先处理大小在区间[0,5)内的数据,再处理大小在区间[5,10)内的数据。
由于这两段的范围大小是一样的,只是起始点不同,因此,可以同用一个Hash表,只需要改动一下Hash函数就可以了。
wuliming_sc 2007-10-15
  • 打赏
  • 举报
回复
[size=24px]嗯,通过楼上的办法可以解决该问题,思路也比较清楚。
是否还有更优更高效的解决办法呢?[/
size]
skyspark 2007-10-15
  • 打赏
  • 举报
回复
to medie2005

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

请指点
ERR0RC0DE 2007-10-15
  • 打赏
  • 举报
回复
我觉得楼主这题跟之发的那个“从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的数字。

大概这样,好像有点麻烦
medie2005 2007-10-15
  • 打赏
  • 举报
回复
搞错了,分配512M内存的话,应该是要处理32次.
medie2005 2007-10-15
  • 打赏
  • 举报
回复
如果内存无限制的话,直接分配4*2^32=16G的内存,直接Hash映射,然后用基于位查找的第k大元素查找算法,找到前100位的数据就可以。

如果16G太大,也可以降低为8G、4G、2G、1G等等,按照上面的方法分段处理就可以了。
medie2005 2007-10-15
  • 打赏
  • 举报
回复
假设元素是非负数,且取值范围是[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位数据。

wuliming_sc 2007-10-15
  • 打赏
  • 举报
回复
再假设内存无明确的限制,但是尽量用最有和最高效的办法解决问题
wuliming_sc 2007-10-15
  • 打赏
  • 举报
回复
假设整数以32bit存储
wpp_zyt 2007-10-15
  • 打赏
  • 举报
回复
假设取直范围是2^32,内存1GB。,怎么利用hash解决啊?
medie2005 2007-10-15
  • 打赏
  • 举报
回复
Tiger_Zhao 说的对。
时间太仓促,忽略了这个问题,谢谢指教。

另:List[]的大小可以直接设置为100,第一段扫描完后,将List[]按分量freq的大小建成最小堆。
不过,3200个单元的unsigned int与512M比起来,实在算不上什么。
Tiger_Zhao 2007-10-15
  • 打赏
  • 举报
回复
分段统计是合理的。还有考虑到数据量很大,在硬盘允许的情况下先按值的范围划分归到不同的文件中,然后在对已划分的文件逐个统计会好一点。毕竟 读2*10亿个+写1*10亿个 应该快于 读32*10亿个 吧。
ahjoe 2007-10-15
  • 打赏
  • 举报
回复
先排序,再来数个数。
那么多数肯定是存在磁盘上的吧,排序的时候也要利用磁盘。
先分范围保存到不同的文件,再每个文件各自排序,最后合并保存到一个文件。
10亿个数就是1G, 如果是32位数(4字节), 总共大小就是4G。如果计划占用内存512M,理想的情况就分8个范围。
因楼主并没有说明数值的分布情况,所以要考虑某些极端的情况,比如这10亿个数,全部相同,或绝大部分是同一个数数值。实现起来还是有点麻烦
medie2005 2007-10-15
  • 打赏
  • 举报
回复
ls对。
xdspower 2007-10-14
  • 打赏
  • 举报
回复
这个问题关键的是整数的取值范围。

33,006

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧