Wie verwende ich eine PriorityQueue?

374

Wie kann ich PriorityQueuesortieren, wonach es sortiert werden soll?

Gibt es auch einen Unterschied zwischen den Methoden offerund add?

Svish
quelle

Antworten:

449

Verwenden Sie die Konstruktorüberladung, die a benötigt, Comparator<? super E> comparatorund übergeben Sie einen Komparator, der auf die für Ihre Sortierreihenfolge geeignete Weise vergleicht. Wenn Sie ein Beispiel für die Sortierung angeben, können wir einen Beispielcode zur Implementierung des Komparators bereitstellen, wenn Sie sich nicht sicher sind. (Es ist allerdings ziemlich einfach.)

Wie bereits an anderer Stelle gesagt: offerund addsind nur verschiedene Implementierungen von Schnittstellenmethoden. In der JDK-Quelle habe ich addAnrufe offer. Obwohl addund offerhat möglicherweise für eine anderes Verhalten im Allgemeinen aufgrund der Fähigkeit , offerum anzuzeigen , dass der Wert nicht aufgrund von Größenbeschränkungen hinzugefügt wird, ist dieser Unterschied unerheblich in PriorityQueuewelcher unbeschränkt ist.

Hier ist ein Beispiel für eine Prioritätswarteschlange, die nach Zeichenfolgenlänge sortiert ist:

// 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;
    }
}

Hier ist die Ausgabe:

kurz

Mittel

sehr lange in der Tat

Jon Skeet
quelle
7
Hmm ... gerade bemerkt ... priorityQueue.comparator () "Gibt den Komparator zurück, der zum Bestellen dieser Sammlung verwendet wird, oder null, wenn diese Sammlung nach ihrer natürlichen Reihenfolge sortiert ist (unter Verwendung von Comparable)." Heißt das, ich könnte Comparable auch in meiner Klasse implementieren?
Svish
7
Du könntest ja. Ich würde es nicht tun, wenn es nicht eine einzige natürliche Sortierreihenfolge für Ihre Klasse gibt. Wenn ja, ist das das Richtige :)
Jon Skeet
8
Sollte die compareImplementierung nicht einfach sein return x.length() - y.length()? (Vermeidet Verzweigungsvorhersage)
Franky
7
@Franky: Es könnte sein, ja - obwohl ich sagen würde, dass das etwas schwieriger zu verstehen ist und der Zweck der Antwort darin besteht, zu demonstrieren, wie es funktioniert. Ich werde jedoch einen Kommentar hinzufügen.
Jon Skeet
2
@KarelG: Ich denke nicht, dass es zu wichtig ist, solange Sie sich der Unterschiede bewusst sind. Ich denke, wenn Sie add()für den Addiervorgang verwenden, dann remove()fühlt es sich vernünftig an; Wenn ich verwenden offer()würde, würde ich wahrscheinlich verwenden poll()... aber das ist nur eine persönliche Präferenz.
Jon Skeet
68

Java 8-Lösung

Wir können Java 8 verwenden lambda expressionoder method referencein Java 8 einführen. Falls einige String-Werte in der Prioritätswarteschlange gespeichert sind (mit Kapazität 5), können wir einen Inline-Komparator (basierend auf der Länge des Strings) bereitstellen:

Verwenden des Lambda-Ausdrucks

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

Verwenden der Methodenreferenz

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

Dann können wir jeden von ihnen verwenden als:

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());
        }
    }

Dies wird gedruckt:

Apple
PineApple
Custard Apple

Um die Reihenfolge umzukehren (um sie in die Warteschlange mit maximaler Priorität zu ändern), ändern Sie einfach die Reihenfolge im Inline-Komparator oder verwenden Sie sie reversedals:

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

Wir können auch verwenden Collections.reverseOrder:

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

Wir können also sehen, dass dies Collections.reverseOrderüberladen ist, um einen Komparator zu verwenden, der für benutzerdefinierte Objekte nützlich sein kann. Das verwendet reversedtatsächlich Collections.reverseOrder:

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

quote () vs add ()

Gemäß dem Dokument

Die Angebotsmethode fügt nach Möglichkeit ein Element ein, andernfalls wird false zurückgegeben. Dies unterscheidet sich von der Collection.add-Methode, bei der ein Element möglicherweise nur durch Auslösen einer nicht aktivierten Ausnahme nicht hinzugefügt werden kann. Die Angebotsmethode ist für die Verwendung konzipiert, wenn ein Fehler eher normal als außergewöhnlich ist, z. B. in Warteschlangen mit fester Kapazität (oder "begrenzt").

Wenn Sie eine Warteschlange mit eingeschränkter Kapazität verwenden, ist Offer () im Allgemeinen Add () vorzuziehen, wodurch ein Element möglicherweise nur durch Auslösen einer Ausnahme nicht eingefügt werden kann. Und PriorityQueue ist eine unbegrenzte Prioritätswarteschlange, die auf einem Prioritätsheap basiert.

akhil_mittal
quelle
Ich gehe davon aus, 5dass die Startkapazität der Warteschlange angibt?
Neil
1
@Neil Ja, ich habe es jetzt in der Antwort expliziter gemacht :)
akhil_mittal
1
Die 8. Version von Java war das Beste, was der Sprache jemals passiert ist
GabrielBB
1
Sehr schöne Erklärung mit klarem Beispiel.
Vishwa Ratna
24

Übergeben Sie es einfach Comparatoran den Konstruktor :

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

Der einzige Unterschied zwischen offerund addist die Schnittstelle, zu der sie gehören. offergehört zu Queue<E>, während addursprünglich in der Collection<E>Schnittstelle gesehen wird. Abgesehen davon machen beide Methoden genau dasselbe - fügen Sie das angegebene Element in die Prioritätswarteschlange ein.

Libelle
quelle
7
Insbesondere löst add () eine Ausnahme aus, wenn Kapazitätsbeschränkungen verhindern, dass das Element zur Warteschlange hinzugefügt wird, während das Angebot false zurückgibt. Da PriorityQueues keine maximale Kapazität haben, ist der Unterschied umstritten.
James
Das ist eine sehr klare Unterscheidung zwischen add () und quote (). Und add () musste trotzdem implementiert werden!
Mit dem
19

von der Warteschlangen-API :

Die Angebotsmethode fügt nach Möglichkeit ein Element ein, andernfalls wird false zurückgegeben. Dies unterscheidet sich von der Collection.add-Methode, bei der ein Element möglicherweise nur durch Auslösen einer nicht aktivierten Ausnahme nicht hinzugefügt werden kann. Die Angebotsmethode ist für die Verwendung konzipiert, wenn ein Fehler eher normal als außergewöhnlich ist, z. B. in Warteschlangen mit fester Kapazität (oder "begrenzt").

Peter
quelle
12

nicht anders, wie in javadoc zu erklären:

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

Nur um die add()vs- offer()Frage zu beantworten (da die andere imo perfekt beantwortet ist und dies möglicherweise nicht der Fall ist):

Laut JavaDoc in der Schnittstellenwarteschlange "fügt die Angebotsmethode nach Möglichkeit ein Element ein und gibt andernfalls false zurück. Dies unterscheidet sich von der Collection.add-Methode, bei der ein Element möglicherweise nur durch Auslösen einer nicht aktivierten Ausnahme nicht hinzugefügt werden kann. Die Angebotsmethode ist für ausgelegt Verwendung, wenn ein Fehler eher normal als außergewöhnlich ist, z. B. in Warteschlangen mit fester Kapazität (oder "begrenzten"). "

Das heißt, wenn Sie das Element hinzufügen können (was in einer PriorityQueue immer der Fall sein sollte), funktionieren sie genau gleich. Wenn Sie das Element jedoch nicht hinzufügen offer()können, erhalten Sie eine schöne und hübsche falseRendite, während Sie add()eine böse, nicht aktivierte Ausnahme auslösen, die Sie nicht in Ihrem Code haben möchten. Wenn das Hinzufügen von Code nicht wie vorgesehen funktioniert und / oder normalerweise überprüft wird, verwenden Sie offer(). Wenn das Hinzufügen nicht dazu führt, dass etwas nicht funktioniert, verwenden add()und behandeln Sie die resultierende Ausnahme, die gemäß den Spezifikationen der Collection-Schnittstelle ausgelöst wird .

Sie werden beide auf diese Weise implementiert, um den Vertrag auf der Warteschlangenschnittstelle, die Fehler angibt offer(), durch Rückgabe einer false( in Warteschlangen mit eingeschränkter Kapazität bevorzugten Methode ) zu erfüllen und den Vertrag auf der Sammlungsschnittstelle, die angibt, add()immer fehlzuschlagen, indem eine Ausnahme ausgelöst wird .

Wie auch immer, ich hoffe, das klärt zumindest diesen Teil der Frage.

Blueriver
quelle
6

Hier können wir einen benutzerdefinierten Komparator definieren:

Unter Code:

 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());
      }
   }  
}  

Ausgabe :

   india                                               
   pakistan                                         
   bangladesh

Unterschied zwischen Angebot und Hinzufügungsmethode: Link

Hautausschläge
quelle
1
Was ist, wenn sie gleich sind?
Nycynik
4

Übergeben Sie es a Comparator. Geben Sie anstelle von den gewünschten Typ einT

Verwenden von Lambdas (Java 8+):

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

Klassische Methode mit anonymer Klasse:

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

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

});

Um in umgekehrter Reihenfolge zu sortieren, tauschen Sie einfach e1, e2 aus.

James Wierzba
quelle
3

Ich habe mich auch über die Druckreihenfolge gewundert. Betrachten Sie diesen Fall zum Beispiel:

Für eine Prioritätswarteschlange:

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

Dieser Code:

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

kann anders drucken als:

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

Ich habe die Antwort aus einer Diskussion in einem anderen Forum gefunden , in der ein Benutzer sagte: "Die Methode quote () / add () fügt das Element nur in die Warteschlange ein. Wenn Sie eine vorhersehbare Reihenfolge wünschen, sollten Sie peek / poll verwenden, die den Kopf zurückgeben der Warteschlange. "

Joserey
quelle
3

Alternativ zur Verwendung Comparatorkönnen Sie auch die Klasse verwenden, die Sie in Ihrem PriorityQueue Implementierungsprogramm verwendenComparable (und die compareToMethode entsprechend überschreiben ).

Beachten Sie, dass es im Allgemeinen am besten ist, nur zu verwenden, Comparableanstatt, Comparatorwenn diese Reihenfolge die intuitive Reihenfolge des Objekts ist. Wenn Sie beispielsweise einen Anwendungsfall zum Sortieren von PersonObjekten nach Alter haben, ist es wahrscheinlich am besten, Comparatorstattdessen nur zu verwenden .

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;
    }
}

Ausgabe:

1, short
1, medium
1, very long indeed
2, short
2, medium
2, very long indeed
Bernhard Barker
quelle
1

Der Prioritätswarteschlange ist jedem Element eine Priorität zugewiesen. Das Element mit der höchsten Priorität wird oben in der Warteschlange angezeigt. Nun hängt es von Ihnen ab, wie jedem Element Priorität zugewiesen werden soll. Wenn Sie dies nicht tun, wird Java dies standardmäßig tun. Das Element mit dem geringsten Wert erhält die höchste Priorität und wird daher zuerst aus der Warteschlange entfernt. Wenn es mehrere Elemente mit derselben höchsten Priorität gibt, wird die Bindung willkürlich unterbrochen. Sie können eine Reihenfolge auch mit Comparator im Konstruktor angeben PriorityQueue(initialCapacity, comparator)

Beispielcode:

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() + " ");
}

Ausgabe:

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

Andernfalls können Sie auch einen benutzerdefinierten Komparator definieren:

import java.util.Comparator;

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

Hier ist das einfache Beispiel, das Sie für das anfängliche Lernen verwenden können:

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());
        }
    }

}
KayV
quelle