金山的笔试题

一个金山的笔试题:

 

有一个日志文件,每行记录了一次调用信息,其中包括时间和来源IP。每天的记录数目大约10亿条左右。现在需要:
1
)获取日访问次数最高的1000个来源IP,按照访问量从高到低排序。
2
)获取连续一周内访问次数最高的1000个来源IP,按照访问量从高到低排序。
请给出能得到精确(非近似)结果,并且效率尽可能高的计算方法,并给出主要部分伪代码。

 

大数据量的问题,面临以下几个问题:

一、10亿条数据中最多有多少条IP记录。考虑是否可以一次将记录读入内存,进行排序

二、IP记录过大,无法完全读入内存,则考虑外部排序。

三、考虑算法的优化,效率问题

四、根据实际的问题,分析考虑如何实现该算法。

五、是否可已导入数据库,使用数据库操作来实现(其实大部分公司,对日志的分析,都是导入的数据库中进行,并优化数据库操作,减低数据库压力)

 

 

l         不考虑内存空间是否能承受该10亿天记录

 

Ø         堆实现

用堆的方式保存源地址信息,每个节点的结构为
typedef unsigned long U32;
struct NODE
{
  U32 ip; //
IP地址
  U32 visitToday; //
本日访问次数
  U32 visitThisWeek; //
本周访问次数
};

伪码:

U32 ip;
TIME time;

//
ipkey建立堆

ret = ReadLine(&ip, &time);
while (ret != EOF)
{
  struct NODE *q = binarySearchWithIp(ip);
  if (NULL == q)
  {
  q = GetHeapTailNode();
  q->ip = ip;
  q->visitToday = 0;
  q->visitThisWeek = 0;
  AdjustHeapWithIp();
  }
  if (IsToday(time)) (p->visitToday)++;
  is (IsThisWeek(time)) (p->visitThisWeek)++;

  ret = ReadLine(&ip, &time);
}

//
visitTodayKey对堆进行重新排序,只要排出前1000个即可
SortHeapTopNWithVisitToday(1000);
PrintHeapTopN(1000);

//
visitThisWeekKey对堆进行重新排序,只要排出1000个即可
SortHeapTopNWithVisitThisWeek(1000);
PrintHeapTopN(1000);

 

l         外部存储的散列

我觉得要得出精确的结果,这个问题要分解成两个问题
1.
统计ip访问量日访问量
2.
进行排序

问题1,确实无法在内存里完成统计,需要借助外部存储,我暂时能想到的高效的文本文件结构式是根据ip进行散列,例如127.0.0.1,将散列到文件/127/0/0/ip.dat这个文件中,ip.dat中将存储255个记录,记录,均按照24位二进制存放,24位中头8位为一个整形用于存放ip最后一段,后16为为长整形用于存放访问次数,ip.dat 在创建的时候将直接填满 24*255的长度,根据ip的最后一段进行文件中的位置定位,这样就能够知道这条记录在ip.dat中的文位置,便于改写(例如127.0.0.1,1就在这个文件中的首位,127.0.0.129就在 129*24的位置上)。在统计的过程中,要注意一次要统计一定的记录数量后,在写入磁盘,这样可以降低磁盘读写次数,例如,做一个位图,keyip,value为次数,遍历日志文件,当这个位图中的 key超过了10万条则写入磁盘。

问题2,实际上这个问题是如何收集1000条值最高的记录,而不应该是 所有的记录进行全部的排序,这样的话,可以通过一个大根堆,来进行过滤,每当堆内的节点达到1001的时候则将最小的remove掉,这样就将大数据量的排序的问题转化为小数据量排序,在遍历了一遍step1中的统计结果后,就能够得出准确的结果了

 

 

l         算法设计与分析方面

分成2个步骤,第一个步骤是分类统计
如果内存足够,就使用数组,232次方大小的UINT数组,也就是16GB
如果内存不够,又不会出现太坏的情况,就用平衡树。(由于内存分配的原因,但如果出现最坏或者很坏的情况,比如10亿个IP中有1亿个以上不同的IP,平衡树实际所需要的内存反而比数组要大)

第二个步骤,就是在第一个步骤的基础上进行排序,找出最多的1000IP
最容易想到的就是快速排序,时间复杂度是 O(n*log2n)。堆排序的效率并不比快速排序高。


但这里的题目并不是要求我们对所有的IP进行排序,只是求出前1000IP。所以,对于第二步,有另外一种思路,可以更高效的获得结果。
这里做一个假设,最后10亿个IP中有1亿个不同的IP。需要在1亿个IP中找到1000IP
对于快速排序来说复杂度就是: N*log2NN=1亿)。大概等于26亿。

这里不直接使用快速排序,而是分为三个步骤。
步骤2.1:
IP1,IP2......IP100000000.
现在IP1IP2比较,IP3IP4比较,IP99999999IP100000000比较,得到较大的IP

假设是IP2,IP4,IP5,IP8.......IP99999999.继续22个的比较,得到较大的IP
假设是IP2,IP8......IP99999999
这样经过M次比较(这里M=13),最后得到了大概1万个左右的IP
步骤2.1复杂度是固定的,并且小于NN=1亿)

步骤2.2:
这个时候对这1万个左右的IP进行快速排序,复杂度是 X*log2XX=1万),找出前1000IP


步骤2.3:
那么剩下1000IP,对于那大概9000个左右的IP,以及被这9000IP淘汰掉的IP,都可以扔掉了。

我们只需要考虑1000IP以及和被他们淘汰的IP,也就是1000*2M次方(这里M=13),总共大概是800万个IP
这个时候对800万个IP进行快速排序,复杂度是 Y*log2YY=800万)

最终的时间复杂度是步骤2.1,2.2,2.3的总和,这里得到的结果大概是:1亿(步骤1+ 13万(步骤2 + 1亿8千万(步骤3),大概是3亿不到。


补充:对于步骤2,我这里M=13只是随意给出的一个方案,对于不同的情况,M都不一样,可以通过复杂的计算得到M的最优值。另外,对于步骤2.3,实际上也可以递归分解为和步骤2一样的3个步骤。

 

 

数据量这么大,无论是用什么工具去处理都是不太好处理的

我想法

1)
它是按时间来进行统计的.所以,第一步,我觉得需要把这个十几亿或者是过百亿行的文件拆分了多个文件.可以按每小时或者每半小时为一个文件

2)
当拆分了多个文件后,每个文件中的IP进行统计,并且记录在持久性介质中
3)
重持久性介质中,再通过 Hash 等方式来进行排序 

总结: 到了10亿这个层面上,常用算法都是变得相当无力
.
我思想是现实有限硬件条件下,可以对大数据量处理进行拆分,统计,再重组来处理
.
若假象在无限制硬件条件下,哈希的方式应该是最快吧.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值