从非常大的未排序列表中获取最大X数字的最快方法?

时间:2009-10-21 19:17:43

标签: c++ optimization visual-c++ sorting

我正试图从我的计划生成的分数列表中获得最高分100分。不幸的是,这个列表非常庞大(大约数百万到数十亿),因此排序是该计划的一个时间密集型部分。

进行排序以获得前100名得分的最佳方式是什么?

到目前为止,我能想到的唯一两种方法是首先将所有分数生成一个大型数组,然后对其进行排序并取得前100名。或者第二,生成X个分数,对其进行排序并截断前100个然后分数继续生成更多分数,将它们添加到截断列表中,然后再次对其进行排序。

无论哪种方式我都这样做,它仍然需要比我想要更多的时间,任何关于如何以更有效的方式做到这一点的想法? (我之前从未参加过编程课程,也许那些有comp sci学位的人都知道有效的算法来做到这一点,至少那是我所希望的。)

最后,c ++中的标准sort()函数使用的排序算法是什么?

谢谢,

-Faken

编辑:只为那些好奇的人......

我在之前和之后做了几次试验,结果如下:

旧程序(在每次外循环迭代后预先排序):

top 100 scores: 147 seconds
top  10 scores: 147 seconds
top   1 scores: 146 seconds
Sorting disabled: 55 seconds

新程序(仅实现对最高分的跟踪并使用默认排序功能):

top 100 scores: 350 seconds <-- hmm...worse than before
top  10 scores: 103 seconds 
top   1 scores:  69 seconds 
Sorting disabled: 51 seconds

新的重写(存储数据的优化,手写排序算法):

top 100 scores: 71 seconds <-- Very nice!
top  10 scores: 52 seconds
top   1 scores: 51 seconds
Sorting disabled: 50 seconds

完成核心2,1.6 GHz ...我不能等到我的核心i7 860到来......

还有很多其他更积极的优化让我解决(主要是在减少我运行的迭代次数的领域),但是就目前看来,速度已经足够好了,我可能不会甚至懒得去理算那些算法优化。

感谢eveyrone的投入!

11 个答案:

答案 0 :(得分:25)

  1. 取前100个分数,然后按数组排序。
  2. 获取下一个分数,然后将其插入数组(从“小”端开始)
  3. 删除第101个值
  4. 继续下一个值,为2,直到完成
  5. 随着时间的推移,列表将越来越类似于100个最大值,因此更常见的是,您发现插入排序会立即中止,发现新值小于前100个候选项的最小值。

答案 1 :(得分:7)

您可以在O(n)时间内完成此操作,无需使用堆进行任何排序:

#!/usr/bin/python

import heapq

def top_n(l, n):
    top_n = []

    smallest = None

    for elem in l:
        if len(top_n) < n:
            top_n.append(elem)
            if len(top_n) == n:
                heapq.heapify(top_n)
                smallest = heapq.nsmallest(1, top_n)[0]
        else:
            if elem > smallest:
                heapq.heapreplace(top_n, elem)
                smallest = heapq.nsmallest(1, top_n)[0]

    return sorted(top_n)


def random_ints(n):
    import random
    for i in range(0, n):
        yield random.randint(0, 10000)

print top_n(random_ints(1000000), 100)

我机器上的时间(Core2 Q6600,Linux,Python 2.6,用bash time内置测量):

  • 100000个元素:.29秒
  • 1000000元素:2.8秒
  • 10000000元素:25.2秒

编辑/添加:在C ++中,您可以使用std::priority_queue,就像使用Python的heapq模块一样。您将要使用std::greater排序而不是默认std::less,以便top()成员函数返回最小元素而不是最大元素。 C ++的优先级队列没有等同于heapreplace,它将顶部元素替换为新元素,因此您需要pop顶部(最小)元素,然后{{1}新见的价值。除此之外,算法非常干净地从Python转换为C ++。

答案 2 :(得分:5)

以下是“自然的”C ++方法:

std::vector<Score> v;
// fill in v
std::partial_sort(v.begin(), v.begin() + 100, v.end(), std::greater<Score>());
std::sort(v.begin(), v.begin() + 100);

这是分数的线性。

std :: sort使用的算法不是由标准规定的,但是libstdc ++(由g ++使用)使用“自适应内向”,这实际上是一个中等于3的快速入口,直到某个级别,然后通过插入排序。

答案 3 :(得分:4)

声明一个数组,你可以在其中获得100个最佳分数。循环浏览巨大的列表并检查每个项目是否有资格插入前100个。使用简单的插入排序将项目添加到顶部列表。

像这样(C#代码,但你明白了):

Score[] toplist = new Score[100];
int size = 0;
foreach (Score score in hugeList) {
   int pos = size;
   while (pos > 0 && toplist[pos - 1] < score) {
      pos--;
      if (pos < 99) toplist[pos + 1] = toplist[pos];
   }
   if (size < 100) size++;
   if (pos < size) toplist[pos] = score;
}

我在计算机上测试了它(Code 2 Duo 2.54 MHz Win 7 x64),我可以在369毫秒内处理100.000.000个项目。

答案 4 :(得分:3)

由于速度至关重要,并且当今任何一台计算机都可以完全维护40.000个可能的高分值,为了简单起见,我会采用桶式排序。我的猜测是,它将超越迄今为止提出的任何算法。缺点是你必须确定高分值的一些上限。

所以,我们假设你的最高得分值是40.000:

制作一个包含40.000个条目的数组。循环显示您的高分值。每次遇到高分x时,将数组[x]增加1。在此之后,您所要做的就是计算数组中的顶部条目,直到达到100个计算的高分数。

答案 5 :(得分:1)

你可以在Haskell中这样做:

largest100 xs = take 100 $ sortBy (flip compare) xs

这看起来像是按降序对所有数字进行排序(“翻转比较”位将标准比较函数的参数反转),然后从列表中返回前100个条目。但是Haskell被懒惰地评估了,所以sortBy函数只进行了足够的排序以找到列表中的前100个数字,然后停止。

纯粹主义者会注意到你也可以把这个函数写成

largest100 = take 100 . sortBy (flip compare)

这意味着同样的事情,但是说明了Haskell风格,它是用其他函数的构建块来组成一个新函数,而不是在这个地方处理变量。

答案 6 :(得分:0)

你想要绝对最大的X数,所以我猜你不想要某种启发式。列表怎么没有排序?如果它是随机的,那么你最好的选择就是对整个列表进行快速排序并获得前X个结果。

如果您可以在列表生成期间过滤分数,那就更好了。只存储X值,每次获得新值时,将其与这些X值进行比较。如果它少于所有这些,就扔掉它。如果它大于其中一个,则抛出新的最小值。

如果X足够小,您甚至可以对X值列表进行排序,以便将新数字与已排序的值列表进行比较,您可以进行O(1)检查以查看新值是否更小而不是所有其余的,因此扔出去。否则,快速二进制搜索可以找到新值在列表中的位置,然后您可以丢弃数组的第一个值(假设第一个元素是最小元素)。

答案 7 :(得分:0)

将数据放入平衡的树结构(可能是红黑树),进行排序。插入应为O(lg n)。抓住最高的x分也应该是O(lg n)。

如果您发现某些时候需要优化,可以每隔一段时间修剪一次树。

答案 8 :(得分:0)

如果您只需要报告前100个分数(而不是任何相关数据)的值,并且如果您知道分数将全部处于有限范围内,例如[0,100],那么这是一种简单的方法与“计数排序”......

基本上,创建一个表示所有可能值的数组(例如,如果分数范围为0到100,则为101的数组),并使用值0初始化数组的所有元素。然后,遍历列表得分,增加得分的列表中的相应条目。也就是说,编译该范围内每个分数的实现次数。然后,从数组的末尾到数组的开头,你可以选出最高的X分数。这是一些伪代码:

    let type Score be an integer ranging from 0 to 100, inclusive.
    let scores be an array of Score objects
    let scorerange be an array of integers of size 101.

    for i in [0,100]
        set scorerange[i] = 0

    for each score in scores
        set scorerange[score] = scorerange[score] + 1

    let top be the number of top scores to report
    let idx be an integer initialized to the end of scorerange (i.e. 100)

    while (top > 0) and (idx>=0):
        if scorerange[idx] > 0:
              report "There are " scorerange[idx] " scores with value " idx
              top =  top - scorerange[idx]
        idx = idx - 1;

答案 9 :(得分:0)

我在回答2008年的面试问题时回答了这个问题。我实施了templatized priority queue in C#

using System;
using System.Collections.Generic;
using System.Text;

namespace CompanyTest
{
    //  Based on pre-generics C# implementation at
    //      http://www.boyet.com/Articles/WritingapriorityqueueinC.html
    //  and wikipedia article
    //      http://en.wikipedia.org/wiki/Binary_heap
    class PriorityQueue<T>
    {
        struct Pair
        {
            T val;
            int priority;
            public Pair(T v, int p)
            {
                this.val = v;
                this.priority = p;
            }
            public T Val { get { return this.val; } }
            public int Priority { get { return this.priority; } }
        }
        #region Private members
        private System.Collections.Generic.List<Pair> array = new System.Collections.Generic.List<Pair>();
        #endregion
        #region Constructor
        public PriorityQueue()
        {
        }
        #endregion
        #region Public methods
        public void Enqueue(T val, int priority)
        {
            Pair p = new Pair(val, priority);
            array.Add(p);
            bubbleUp(array.Count - 1);
        }
        public T Dequeue()
        {
            if (array.Count <= 0)
                throw new System.InvalidOperationException("Queue is empty");
            else
            {
                Pair result = array[0];
                array[0] = array[array.Count - 1];
                array.RemoveAt(array.Count - 1);
                if (array.Count > 0)
                    trickleDown(0);
                return result.Val;
            }
        }
        #endregion
        #region Private methods
        private static int ParentOf(int index)
        {
            return (index - 1) / 2;
        }
        private static int LeftChildOf(int index)
        {
            return (index * 2) + 1;
        }
        private static bool ParentIsLowerPriority(Pair parent, Pair item)
        {
            return (parent.Priority < item.Priority);
        }
        //  Move high priority items from bottom up the heap
        private void bubbleUp(int index)
        {
            Pair item = array[index];
            int parent = ParentOf(index);
            while ((index > 0) && ParentIsLowerPriority(array[parent], item))
            {
                //  Parent is lower priority -- move it down
                array[index] = array[parent];
                index = parent;
                parent = ParentOf(index);
            }
            //  Write the item once in its correct place
            array[index] = item;
        }
        //  Push low priority items from the top of the down
        private void trickleDown(int index)
        {
            Pair item = array[index];
            int child = LeftChildOf(index);
            while (child < array.Count)
            {
                bool rightChildExists = ((child + 1) < array.Count);
                if (rightChildExists)
                {
                    bool rightChildIsHigherPriority = (array[child].Priority < array[child + 1].Priority);
                    if (rightChildIsHigherPriority)
                        child++;
                }
                //  array[child] points at higher priority sibling -- move it up
                array[index] = array[child];
                index = child;
                child = LeftChildOf(index);
            }
            //  Put the former root in its correct place
            array[index] = item;
            bubbleUp(index);
        }
        #endregion
    }
}

答案 10 :(得分:0)