在M个已知数中就N个最大数(ZZ)

这个问题来自于寒枫天伤的一个post:一个网友的面试题。这里假设M是一个相当大的数,N是相对小很多的常数。例如,在几百万个已知数中求10个最大的数。在寒枫天伤的entry后面,不少人都通过comment给出了自己的想法,我也曾被问过这个题目,在这里说说我的思路。

我基本上比较同意fan1的说法:

设置10个变量,最开始取头10个数字将10个变量填满,并进行排序,然后对一百万个数字进行一次遍历,每一个数字先比对10个变量的最小值,如果结果为小,则进行下一个数字,反之则淘汰当前最小变量,然后将当前数字插入适当位置,之后继续下一个数字比较。

后面,尉迟方做了这样的反驳:

呵呵fans1的算法应该不是最优的吧,只看到他一个一个的往里塞 怎么塞进去的却不管了 最坏情况是每个都要插入&移动10次,那就是10 × n。加上查找位置的比较时间,二分好了,算最坏的,4次,就是4 × n

我认为,这个插入和查找的过程是可以统一起来的,查找的问题是不存在的。当然最坏情况是要做10次的比较和移动,但是因为N相对于M是一个很小的数字,所以,算法复杂度仍然是会在O(n)规模上。

还有一位叫Xiaochengyong的朋友提出这样的方法:

使用类似Radix Sorting 的方法。就像从一个班的考试试卷找出前10名一样。 一、第一次遍历,从1到n,将试卷按照0-10分、10-20、20-30、...、80-90、90-100分进行分别堆放。 二、考察90-100中的元素数目, 如果大于10,则只考虑90-100分的区段,这样就丢掉了0-90分的试卷;则如果小于10,例如只有3份,再考察80-90分区段依次类推。 继续使用该算法,对90-100分的区段进行分类,例如91-92、...,99-100,依次类推... 就本题目来说,一百万个元素(假设每个元素在0-100之间),在第一次从1到n的遍历后,就丢掉了0-90的大量元素,依次下去,每次都可以丢掉局部元素中的较大部分元素。这种算法的复杂度大概应该是T(n)=n+n/10+n/(10*10)+...n/(10*...10)。

我认为,首先一点这M个数分布区域可能非常大,不能假设它们处于1-100之间,其次,按照基数排序的方式,是否需要辅助存储空间,这个空间复杂度似乎作者也没有考虑。

所以,我当时的答案也和fan1一样。并且,在排序的临时存储空间边界上设定一个“哨兵”,可以减少比较的次数,对于如此大规模的运算过程,效率上的提高是非常显著的。按照这个思路,写了下面的这段小程序,其中data[]为数据样本,top[]用于存放最大的N个数的辅助空间:

int data[M];
int top[N + 1];

void top_N()
{
    top[0] = std::numeric_limits<int>::max(); // Set a "guard" on the boundary to reduce comparision times.
    for (int i = 1; i <= N; ++i) top[i] = std::numeric_limits<int>::min();
    for (int j = 0; j < M; ++j)
    {
        for (int k = N; top[k] < data[j]; --k) top[k] = top[k - 1];
        top[k + 1] = data[j];
    }
}

我不知道是否还有更优的做法了,我能想到的也就是这个办法了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值