如何使用PriorityQueue?


Answers:


449

使用构造函数重载,该重载采用a Comparator<? super E> comparator并传递一个比较器,该比较器以适合您的排序顺序的方式进行比较。如果您举一个如何排序的例子,如果您不确定,我们可以提供一些示例代码来实现比较器。(虽然非常简单。)

正如其他地方所说:offeradd只是不同的接口方法实现。在JDK源代码中,请add致电offer。尽管由于具有指示由于大小限制而无法添加该值的能力,通常addoffer可能具有不同的行为offer,但是这种差异是无限制的,是无关紧要的PriorityQueue

这是一个按字符串长度排序的优先级队列示例:

// Test.java
import java.util.Comparator;
import java.util.PriorityQueue;

public class Test {
    public static void main(String[] args) {
        Comparator<String> comparator = new StringLengthComparator();
        PriorityQueue<String> queue = new PriorityQueue<String>(10, comparator);
        queue.add("short");
        queue.add("very long indeed");
        queue.add("medium");
        while (queue.size() != 0) {
            System.out.println(queue.remove());
        }
    }
}

// StringLengthComparator.java
import java.util.Comparator;

public class StringLengthComparator implements Comparator<String> {
    @Override
    public int compare(String x, String y) {
        // Assume neither string is null. Real code should
        // probably be more robust
        // You could also just return x.length() - y.length(),
        // which would be more efficient.
        if (x.length() < y.length()) {
            return -1;
        }
        if (x.length() > y.length()) {
            return 1;
        }
        return 0;
    }
}

这是输出:

介质

确实很长


7
嗯...刚刚注意到... priorityQueue.comparator()“返回用于排序此集合的比较器,如果此集合是根据其元素的自然排序(使用Comparable)排序的,则返回null。” 这是否意味着我也可以在类上实现Comparable?
Svish

7
你可以,是的。除非您的班级只有一个自然的排序顺序,否则我不会这样做。如果有的话,那是正确的事情:)
乔恩·斯凯特

8
compare实现不应该是return x.length() - y.length()吗?(避免分支预测)
Franky 2014年

7
@Franky:这可能是的,是的-虽然我会说这是稍微更难理解,答案的目的是展示它是如何工作的。我会添加评论。
乔恩·斯基特

2
@KarelG:只要您知道它们之间的差异,我认为这没什么大不了的。我认为如果您要进行add()加法运算,那remove()会很明智;如果我使用的话,offer()我可能会使用poll()...,但这只是个人喜好。
乔恩·斯基特

68

Java 8解决方案

我们可以使用Java 8 lambda expressionmethod reference在Java 8中引入Java。如果我们在Priority Queue中存储了一些String值(容量为5),我们可以提供内联比较器(基于String的长度):

使用lambda表达式

PriorityQueue<String> pq=
                    new PriorityQueue<String>(5,(a,b) -> a.length() - b.length());

使用方法参考

PriorityQueue<String> pq=
                new PriorityQueue<String>(5, Comparator.comparing(String::length));

然后我们可以将它们中的任何一个用作:

public static void main(String[] args) {
        PriorityQueue<String> pq=
                new PriorityQueue<String>(5, (a,b) -> a.length() - b.length());
       // or pq = new PriorityQueue<String>(5, Comparator.comparing(String::length));
        pq.add("Apple");
        pq.add("PineApple");
        pq.add("Custard Apple");
        while (pq.size() != 0)
        {
            System.out.println(pq.remove());
        }
    }

这将打印:

Apple
PineApple
Custard Apple

要颠倒顺序(将其更改为最大优先级队列),只需在内联比较器中更改顺序或reversed用作:

PriorityQueue<String> pq = new PriorityQueue<String>(5, 
                             Comparator.comparing(String::length).reversed());

我们也可以使用Collections.reverseOrder

PriorityQueue<Integer> pqInt = new PriorityQueue<>(10, Collections.reverseOrder());
PriorityQueue<String> pq = new PriorityQueue<String>(5, 
                Collections.reverseOrder(Comparator.comparing(String::length))

因此,我们可以看到Collections.reverseOrder比较器重载对于自定义对象很有用。在reversed实际使用Collections.reverseOrder

default Comparator<T> reversed() {
    return Collections.reverseOrder(this);
}

offer()vs add()

根据文档

offer方法在可能的情况下插入一个元素,否则返回false。这不同于Collection.add方法,后者只能通过引发未经检查的异常来添加元素。offer方法设计用于在正常情况下(而不是在例外情况下)发生故障(例如在固定容量(或“有界”)队列中)时使用。

当使用容量受限的队列时,offer()通常比add()更可取,因为add()可能仅通过引发异常而无法插入元素。而PriorityQueue中是一个基于优先级堆的极大优先级队列。


我假设5指示队列的起始容量?
尼尔

1
@Neil是的,我现在在回答中更加明确了:)
akhil_mittal '19

1
Java的第8版是该语言发生过的最好的事情
GabrielBB

1
非常清楚的示例说明。
Vishwa Ratna

24

只需将适当Comparator的参数传递给构造函数即可

PriorityQueue(int initialCapacity, Comparator<? super E> comparator)

offer和之间的唯一区别add是它们所属的接口。offer属于Queue<E>,而add最初在Collection<E>接口中看到。除此之外,这两种方法的作用完全相同-将指定的元素插入优先级队列。


7
具体来说,如果容量限制阻止该项目添加到队列,而offer返回false,则add()会引发异常。由于PriorityQueues没有最大容量,因此差别不大。
詹姆斯

这是add()和offer()之间的非常明显的区别。而且无论如何都需要实现add()!
whitehat 2012年

19

来自Queue API

offer方法在可能的情况下插入一个元素,否则返回false。这不同于Collection.add方法,后者只能通过引发未经检查的异常来添加元素。offer方法设计用于在正常情况下(而不是在例外情况下)发生故障(例如在固定容量(或“有界”)队列中)时使用。


12

一样,就像在javadoc中声明的一样:

public boolean add(E e) {
    return offer(e);
}

6

只是为了回答add()vs offer()问题(因为另一个问题在imo上得到了完美的回答,所以可能不是):

根据接口Queue上的JavaDoc的说法,“ offer方法在可能的情况下插入一个元素,否则返回false。这与Collection.add方法不同,Collection.add方法只能通过引发未经检查的异常而无法添加元素。offer方法设计用于当故障是正常情况而不是异常情况发生时使用,例如在固定容量(或“有界”)队列中。”

这意味着,如果您可以添加元素(在PriorityQueue中应该始终如此),则它们的工作原理完全相同。但是,如果您不能添加该元素,offer()则会为您带来漂亮的false回报,同时add()会引发您不想在代码中使用的令人讨厌的未经检查的异常。如果添加失败表示代码按预期工作和/或您会正常检查,请使用offer()。如果添加失败意味着有问题,请使用add()并处理根据Collection接口的规范引发的异常。

它们既以这种方式实现,即offer()通过返回一个false在容量受限的队列中首选的方法)在指定失败的Queue接口上填充合同,也通过在抛出异常的情况下维护指定add()总是失败的Collection接口上的合同。

无论如何,希望至少可以澄清一部分问题。


6

在这里,我们可以定义用户定义的比较器:

下面的代码:

 import java.util.*;
 import java.util.Collections;
 import java.util.Comparator; 


 class Checker implements Comparator<String>
 {
    public int compare(String str1, String str2)
    {
        if (str1.length() < str2.length()) return -1;
        else                               return 1;
    }
 }


class Main
{  
   public static void main(String args[])
    {  
      PriorityQueue<String> queue=new PriorityQueue<String>(5, new Checker());  
      queue.add("india");  
      queue.add("bangladesh");  
      queue.add("pakistan");  

      while (queue.size() != 0)
      {
         System.out.printf("%s\n",queue.remove());
      }
   }  
}  

输出:

   india                                               
   pakistan                                         
   bangladesh

offer和add方法之间的区别:链接


1
如果它们相等,该怎么办。
nycynik

4

通过它Comparator。填写所需的类型,以代替T

使用lambdas(Java 8+):

int initialCapacity = 10;
PriorityQueue<T> pq = new PriorityQueue<>(initialCapacity, (e1, e2) -> { return e1.compareTo(e2); });

经典方法,使用匿名类:

int initialCapacity = 10;
PriorityQueue<T> pq = new PriorityQueue<>(initialCapacity, new Comparator<T> () {

    @Override
    public int compare(T e1, T e2) {
        return e1.compareTo(e2);
    }

});

要以相反的顺序排序,只需交换e1,e2。


3

我也想知道打印顺序。考虑这种情况,例如:

对于优先级队列:

PriorityQueue<String> pq3 = new PriorityQueue<String>();

这段代码:

pq3.offer("a");
pq3.offer("A");

可能会打印出与以下内容不同的内容:

String[] sa = {"a", "A"}; 
for(String s : sa)   
   pq3.offer(s);

在另一个论坛上的讨论中找到了答案,用户说:“ offer()/ add()方法仅将元素插入队列。如果您想获得可预测的顺序,则应使用peek / poll返回头排队。”


3

除了使用之外Comparator,您还可以在自己的PriorityQueue 工具中Comparable使用要使用的类(并相应地重写compareTo方法)。

请注意,通常最好只使用Comparable而不是Comparator如果该排序是对象的直观排序-例如,如果您有一个用例Person按年龄对对象进行排序,则最好只使用它Comparator

import java.lang.Comparable;
import java.util.PriorityQueue;

class Test
{
    public static void main(String[] args)
    {
        PriorityQueue<MyClass> queue = new PriorityQueue<MyClass>();
        queue.add(new MyClass(2, "short"));
        queue.add(new MyClass(2, "very long indeed"));
        queue.add(new MyClass(1, "medium"));
        queue.add(new MyClass(1, "very long indeed"));
        queue.add(new MyClass(2, "medium"));
        queue.add(new MyClass(1, "short"));
        while (queue.size() != 0)
            System.out.println(queue.remove());
    }
}
class MyClass implements Comparable<MyClass>
{
    int sortFirst;
    String sortByLength;

    public MyClass(int sortFirst, String sortByLength)
    {
        this.sortFirst = sortFirst;
        this.sortByLength = sortByLength;
    }

    @Override
    public int compareTo(MyClass other)
    {
        if (sortFirst != other.sortFirst)
            return Integer.compare(sortFirst, other.sortFirst);
        else
            return Integer.compare(sortByLength.length(), other.sortByLength.length());
    }

    public String toString()
    {
        return sortFirst + ", " + sortByLength;
    }
}

输出:

1, short
1, medium
1, very long indeed
2, short
2, medium
2, very long indeed

1

优先级队列为每个元素分配了一些优先级,优先级最高的元素出现在队列顶部。现在,这取决于您如何为每个元素分配优先级。如果您不这样做,Java将以默认方式进行。值最小的元素被分配最高优先级,因此首先从队列中删除。如果存在多个具有相同最高优先级的元素,则关系将被任意打破。您还可以在构造函数中使用Comparator指定顺序 PriorityQueue(initialCapacity, comparator)

示例代码:

PriorityQueue<String> queue1 = new PriorityQueue<>();
queue1.offer("Oklahoma");
queue1.offer("Indiana");
queue1.offer("Georgia");
queue1.offer("Texas");
System.out.println("Priority queue using Comparable:");
while (queue1.size() > 0) {
    System.out.print(queue1.remove() + " ");
}
PriorityQueue<String> queue2 = new PriorityQueue(4, Collections.reverseOrder());
queue2.offer("Oklahoma");
queue2.offer("Indiana");
queue2.offer("Georgia");
queue2.offer("Texas");
System.out.println("\nPriority queue using Comparator:");
while (queue2.size() > 0) {
    System.out.print(queue2.remove() + " ");
}

输出:

Priority queue using Comparable:
Georgia Indiana Oklahoma Texas 
Priority queue using Comparator:
Texas Oklahoma Indiana Georgia 

另外,您还可以定义“自定义比较器”:

import java.util.Comparator;

public class StringLengthComparator implements Comparator<String>
{
    @Override
    public int compare(String x, String y)
    {
        //Your Own Logic
    }
}

1

这是可用于初始学习的简单示例:

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;

public class PQExample {

    public static void main(String[] args) {
        //PriorityQueue with Comparator
        Queue<Customer> cpq = new PriorityQueue<>(7, idComp);
        addToQueue(cpq);
        pollFromQueue(cpq);
    }

    public static Comparator<Customer> idComp = new Comparator<Customer>(){

        @Override
        public int compare(Customer o1, Customer o2) {
            return (int) (o1.getId() - o2.getId());
        }

    };

    //utility method to add random data to Queue
    private static void addToQueue(Queue<Customer> cq){
        Random rand = new Random();
        for(int i=0;i<7;i++){
            int id = rand.nextInt(100);
            cq.add(new Customer(id, "KV"+id));
        }
    }


    private static void pollFromQueue(Queue<Customer> cq){
        while(true){
            Customer c = cq.poll();
            if(c == null) break;
            System.out.println("Customer Polled : "+c.getId() + " "+ c.getName());
        }
    }

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.