【问题标题】:Java - Looking for something faster than PriorityQueueJava - 寻找比 PriorityQueue 更快的东西
【发布时间】:2010-11-24 10:47:04
【问题描述】:

我在大量数据上使用 java。

[我尽量简化问题]

实际上我有一个小类(元素),其中包含一个 int KEY 和一个双 WEIGHT(带有 getter 和 setter)。

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

实际上我正在使用带有 Comparator 的 PriorityQueue 来比较两个元素,它可以工作,但是太慢了。

你知道(我知道你知道)有什么更快的方法吗?

谢谢

【问题讨论】:

  • 您是否对此代码运行了分析器?你的比较器是怎么写的?
  • 我强烈建议您分析您的代码并找出究竟是什么导致您的代码运行得比您喜欢的慢。没有显示代码,也没有其他信息,很难回答这个问题。哪个部分运行缓慢?
  • 旁注:您可以将比较器简化为 return i.getValue()-j.getValue();
  • 正如 Tnay 所指出的,如果没有进一步的分析信息,您就是“在没有表示的情况下进行优化” - 即在不知道问题实际在哪里的情况下进行优化。我建议您购买一个好的分析器 - 我过去使用 YourKit 取得了很大的成功(500-800% 的收益),尽管还有其他 Java 分析器。
  • 使用减法来实现双精度比较器似乎非常冒险。比较器结果被强制转换为 int,因此许多(大多数?全部?)权重之间的显着差异可能被强制为零。

标签: java performance sorting collections priority-queue


【解决方案1】:

除了建议的“peek at the top of the heap”算法(该算法为您提供 O(n log m) 复杂度来获取 n 个项目的 top-m 项)之外,这里还有另外两个解决方案。

解决方案 1:使用斐波那契堆。

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

方案二:遍历列表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) 时间内获得集合中的前 m 个对象,对于足够大的 n 和足够小的 m,这将严格小于 n log n。例如,在所有其他条件相同的情况下,获得超过一百万个项目的前 10 名将花费使用二叉堆优先级队列的一半时间。

【讨论】:

  • 您关于斐波那契堆和二叉堆之间的速度差异因子的说法是假设一个二进制对数并且常数因子没有差异,即它可能是不真实的。
【解决方案2】:

@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))。

这可能会或可能不会执行显着的常数因子加速。 如果将此与埃里克森的解决方案结合起来,可能很难更快地做到这一点(取决于 M 的大小)。如果 M 很大,最有效的解决方案可能是使用 Java 内置的 qsort 对数组中的所有元素进行排序,最后将数组的一端剪掉。

【讨论】:

  • 当然,这个比较器很好,只要保证 i 和 j 之间的差值永远不会超过 Integer.MAX_VALUE。
  • 一般来说,减法是实现浮点值比较的糟糕选择(问题清楚地表明权重是double)。如果差值小于 1,则在将结果转换为 int 时会错误地将其强制为零。
  • @Software Monkey:是的。 @erickson:我没有注意到我们使用的是浮点值。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

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

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

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

    【讨论】:

    • 好建议。该算法的复杂度为 O(n log m),用于获取 n 个总元素中的 top-m。
    猜你喜欢
    • 1970-01-01
    • 2010-10-05
    • 2012-04-15
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多