【问题标题】:Java PriorityQueue with fixed size具有固定大小的 Java PriorityQueue
【发布时间】:2010-12-23 04:55:55
【问题描述】:

我正在计算大量可能的算法结果组合。为了对这些组合进行排序,我用双倍值对它们进行评分,并将它们存储在 PriorityQueue 中。目前,该队列中有大约 20 万个项目,这非常占用内存。实际上,我只需要说列表中所有项目中最好的 1000 个或 100 个。 所以我刚开始问自己是否有办法在 Java 中拥有一个固定大小的优先级队列。我应该这样做: 该项目是否比已存储的项目之一更好?如果是,则将其插入到相应的位置,然后将评分最低的元素扔掉。

有人有想法吗?再次非常感谢!

马可

【问题讨论】:

标签: java list size priority-queue


【解决方案1】:
que.add(d);
if (que.size() > YOUR_LIMIT)
     que.poll();

还是我误解了你的问题?

编辑:忘了提到要让它工作,你可能必须反转你的 comparTo 函数,因为它会丢弃每个周期具有最高优先级的函数。 (如果 a “更好” b 比较 (a, b) 应该返回一个正数。

保持最大数字的示例使用如下:

public int compare(Double first, Double second) {
            // keep the biggest values
            return first > second ? 1 : -1;
        }

【讨论】:

  • 很好的答案,但我更喜欢这个相反的情况: if (que.size() >= YOUR_LIMIT) que.poll(); que.add(d);通过这样做,如果我们将 YOUR_LIMIT 固定为堆的大小,java priorityqueue 将不会调整数组的大小
  • @AnkitBhatnagar,这行不通。这将无条件地移除旧头。 getakha 的回答删除了更糟糕的那个。
  • 这是行不通的,因为人们可能打算将最大值保留在堆(头)的顶部,可以在恒定时间内轮询(),同时在最大大小为时驱逐尾部遇见了。 Guava 的 MinMaxPriorityQueue 可以实现这一点,因为它允许您在恒定时间内到达队列的头部和尾部。
【解决方案2】:

MinMaxPriorityQueue,谷歌番石榴

确实有一个用于维护队列的类,当添加一个将超过集合最大大小的项目时,它会比较项目以找到要删除的项目,从而创建空间:MinMaxPriorityQueueGoogle Guava 中找到从版本 8 开始。

驱逐队列

顺便说一句,如果您只想删除最旧的元素而不对对象的值进行任何比较,Google Guava 15 获得了EvictingQueue 类。

【讨论】:

【解决方案3】:

Apache Lucene 中有一个固定大小的优先级队列:http://lucene.apache.org/java/2_4_1/api/org/apache/lucene/util/PriorityQueue.html

根据我的测试,它具有出色的性能。

【讨论】:

    【解决方案4】:

    使用排序集:

    SortedSet<Item> items = new TreeSet<Item>(new Comparator<Item>(...));
    ...
    void addItem(Item newItem) {
        if (items.size() > 100) {
             Item lowest = items.first();
             if (newItem.greaterThan(lowest)) {
                 items.remove(lowest);
             }
        }
    
        items.add(newItem);   
    }
    

    【讨论】:

    • 一个集合不允许多个Items拥有相同的评分。
    • 取决于您如何为 Set 定义 Comparator -- 它不仅可以考虑评级,还可以考虑 Item 的一些唯一字段,例如 id。
    【解决方案5】:

    只要poll() 队列的最小元素小于(在您的情况下,评级低于)当前元素。

    static <V extends Comparable<? super V>> 
    PriorityQueue<V> nbest(int n, Iterable<V> valueGenerator) {
        PriorityQueue<V> values = new PriorityQueue<V>();
        for (V value : valueGenerator) {
            if (values.size() == n && value.compareTo(values.peek()) > 0)
                values.poll(); // remove least element, current is better
            if (values.size() < n) // we removed one or haven't filled up, so add
                values.add(value);
        }
        return values;
    }
    

    这假设您有某种组合类,它实现了 Comparable 来比较组合的评级。

    编辑: 澄清一下,我的示例中的 Iterable 不需要预先填充。例如,这是一个Iterable&lt;Integer&gt;,它将为您提供int 可以表示的所有自然数:

    Iterable<Integer> naturals = new Iterable<Integer>() {
        public Iterator<Integer> iterator() {
            return new Iterator<Integer>() {
                int current = 0;
                @Override
                public boolean hasNext() {
                    return current >= 0;
                }
                @Override
                public Integer next() {
                    return current++;
                }
                @Override
                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        }
    };
    

    如您所见,内存消耗非常少 - 对于超过 20 亿个值,您需要两个对象(IterableIterator)加上一个 int

    您当然可以相当轻松地调整我的代码,因此它不使用Iterable - 我只是使用它,因为它是一种表示序列的优雅方式(另外,我已经做了太多 Python 和 C# ☺ )。

    【讨论】:

    • 这是否假设您已经拥有valueGenerator 中的所有项目?
    • 我认为 OP 的目标之一是首先避免在 Iterable 中累积如此多的项目。此外,如果排名越高算法越好,那么peek不是你想要的。
    • 不,您不需要让它们都可用。迭代器可以在其next() 方法中动态生成值。
    • 为什么peek() 不成功呢?它返回最小元素,如果当前元素优于最小元素,我将最小元素扔掉并添加当前元素。我测试了代码,它可以工作。
    • 是的,你是对的,头部确实是最少的元素。出于某种原因,我认为情况正好相反。
    【解决方案6】:

    更好的方法是更严格地调节队列中的内容,在程序运行时删除并附加到它。听起来在将某些项目添加到队列之前会有一些空间来排除它们。可以这么说,这比重新发明轮子要简单。

    【讨论】:

      【解决方案7】:

      每次添加项目时只保留前 1000 名似乎很自然,但PriorityQueue 并没有提供任何东西来优雅地实现这一目标。也许您可以在方法中执行类似这样的操作,而不是使用PriorityQueue

      List<Double> list = new ArrayList<Double>();
      ...
      list.add(newOutput);
      Collections.sort(list);
      list = list.subList(0, 1000);
      

      【讨论】:

      • 同样使用 TreeMap,您可以随时获得最高值,如果当前结果大于该值,您可以完全避免插入,否则删除最后一个键并插入新值
      • @Lorenzo, Map 不好,因为它不允许两个组合具有相同的评分。
      • 这种方法没有黑红树实现和性能杀手的性能优势
      • 当您每次对数组进行排序时,这将产生非常糟糕的性能。添加到堆中会更快。
      • 这将大大降低性能,因为我们每次添加元素时都会对列表进行排序。
      猜你喜欢
      • 2011-02-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-13
      • 1970-01-01
      • 2013-01-09
      • 1970-01-01
      相关资源
      最近更新 更多