我正试图从我的计划生成的分数列表中获得最高分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的投入!
答案 0 :(得分:25)
随着时间的推移,列表将越来越类似于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
内置测量):
编辑/添加:在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)