Java - 寻找比PriorityQueue更快的东西

时间:2009-08-31 19:40:34

标签: java performance sorting collections priority-queue

我在大量数据上使用java。

[我尽可能地简化问题]

实际上我有一个小类(Element),包含一个int KEY和一个双重WEIGHT(带有getter和amp setter)。

我从文件中读取了很多这些对象,我必须得到最好的(最重量级)M对象。

实际上我正在使用一个PriorityQueue和一个Comparator来比较两个Element,它可以工作,但它太慢了。

你知道(我知道你这样做)更快的方法吗?

谢谢

4 个答案:

答案 0 :(得分:6)

基于堆的优先级队列是解决此问题的良好数据结构。正如完整性检查一样,验证您是否正确使用了队列。

如果您想要最高权重的项目,请使用 min -queue-其中堆的顶部是最小的项目。将每个项目添加到最大队列并在完成时检查前M个项目效率不高。

对于每个项目,如果队列中的项目少于M,请添加当前项目。否则,偷看堆顶部。如果它小于当前项目,则丢弃它,然后添加当前项目。否则,丢弃当前项目。处理完所有项目后,队列将包含M最高权重项目。

有些堆有快捷API用于替换堆顶部,但Java Queue没有。即便如此,大O的复杂性也是一样的。

答案 1 :(得分:5)

除了建议的“查看堆顶部”算法之外,该算法为获得n个项目的前m个提供了O(n log m)复杂度,这里还有两个解决方案。

解决方案1:使用Fibonacci堆。

JDK的PriorityQueue实现是一个平衡的二进制堆。您应该能够从Fibonacci heap实现中挤出更多性能。它将具有分摊的常量时间插入,而插入二进制堆的堆的大小具有复杂度Ω(log n)。如果你为每个元素做这个,那么你就是Ω(n log n)。使用Fib堆查找n个项目的前m个具有复杂度O(n + m log n)。将此与仅将m个元素插入堆中的建议相结合,并且您有O(n + m log m),这与您将要获得的线性时间接近。

解决方案2:遍历列表M次。

你应该能够在O(n)时间内获得集合中的第k个最大元素。只需将所有内容都读入列表并执行以下操作:

kthLargest(k, xs)
  Pick a random pivot element p from the list
    (the first one will do if your list is already random).
  Go over the set once and group it into two lists.
     Left: smaller than p. 
     Right: Larger or equal to p.
  If the Right list is shorter than k, return kthLargest(k - right.size, Left)
  If the Right list is longer than k, return kthLargest(k, right)
  Otherwise, return p.

这给你O(n)时间。运行m次,您应该能够在时间O(nm)中获得集合中的top-m对象,对于足够大的n和足够小的m,它将严格小于n log n。例如,在使用二进制堆优先级队列时,获得超过一百万个项目的前10个将花费一半,所有其他条件相同。

答案 2 :(得分:2)

如果M适当小,那么对所有元素进行排序可能会浪费大量的计算时间。您只能将前M个对象放在优先级队列中(例如堆,最小元素在顶部),然后迭代其余元素:每次元素大于堆顶部时,删除顶部并推送新元素进入堆。

或者,你可以迭代整个数组一次,找到一个统计阈值,你可以非常肯定有超过M个对象的值更大(需要对这些值进行一些假设,例如,如果它们是正常的分散式)。然后,您可以将排序限制为具有更大值的所有元素。

答案 3 :(得分:0)

@Tnay:你有一个关于不进行比较的观点。不幸的是,您的示例代码仍然执行一个。这解决了这个问题:

public int compare(ListElement i, ListElement j) {
    return i.getValue() - j.getValue();
}

此外,你和BigGs比较方法都不是严格正确的,因为它们永远不会返回0.这可能是一些排序算法的问题,这是一个非常棘手的错误,因为它只会在你切换到另一个时出现实施

来自the Java docs

  

实现者必须确保所有x和y的sgn(compare(x,y))== -sgn(compare(y,x))。

这可能会或可能不会执行显着的恒定因子加速。 如果将此与erickson的解决方案结合使用,可能很难更快地完成(取决于M的大小)。如果M非常大,最有效的解决方案可能是在阵列上使用Java的内置qsort对所有元素进行排序,最后切断阵列的一端。