Làm cách nào để sử dụng PriorityQueue?


374

Làm thế nào để tôi có được một PriorityQueuesắp xếp trên những gì tôi muốn nó sắp xếp trên?

Ngoài ra, có một sự khác biệt giữa offeraddphương pháp?

Câu trả lời:


449

Sử dụng quá tải hàm tạo có một Comparator<? super E> comparatorvà vượt qua trong một bộ so sánh so sánh theo cách thích hợp cho thứ tự sắp xếp của bạn. Nếu bạn đưa ra một ví dụ về cách bạn muốn sắp xếp, chúng tôi có thể cung cấp một số mã mẫu để triển khai trình so sánh nếu bạn không chắc chắn. (Mặc dù nó khá đơn giản.)

Như đã nói ở nơi khác: offeraddchỉ là các triển khai phương thức giao diện khác nhau. Trong nguồn JDK tôi đã nhận, addcác cuộc gọi offer. Mặc dù addofferkhả năng có hành vi khác nhau nói chung do khả năng offerchỉ ra rằng giá trị không thể được thêm vào do giới hạn kích thước, sự khác biệt này không liên quan trong PriorityQueueđó không bị ràng buộc.

Dưới đây là ví dụ về sắp xếp hàng đợi ưu tiên theo độ dài chuỗi:

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

Đây là đầu ra:

ngắn

Trung bình

thực sự rất dài


7
Hmm ... chỉ cần chú ý ... ưu tiên Điều đó có nghĩa là tôi cũng có thể thực hiện So sánh trên lớp của mình chứ?
Svish

7
Bạn có thể, vâng. Tôi sẽ không làm như vậy trừ khi có một thứ tự sắp xếp tự nhiên duy nhất cho lớp của bạn. Nếu có, đó là điều đúng đắn :)
Jon Skeet

8
Không nên comparethực hiện return x.length() - y.length()? (Tránh dự đoán chi nhánh)
Franky

7
@Franky: Có thể là có, mặc dù tôi nói điều đó hơi khó hiểu và mục đích của câu trả lời là thể hiện cách thức hoạt động của nó. Tôi sẽ thêm một bình luận mặc dù.
Jon Skeet

2
@KarelG: Tôi không nghĩ nó quá quan trọng, miễn là bạn nhận thức được sự khác biệt. Tôi nghĩ rằng nếu bạn đang sử dụng add()cho hoạt động thêm, thì remove()cảm thấy hợp lý; nếu tôi đang sử dụng offer()có lẽ tôi sẽ sử dụng poll()... nhưng đó chỉ là một sở thích cá nhân.
Jon Skeet

68

Giải pháp Java 8

Chúng tôi có thể sử dụng lambda expressionhoặc method referencegiới thiệu trong Java 8. Trong trường hợp chúng tôi có một số giá trị Chuỗi được lưu trữ trong Hàng đợi ưu tiên (có dung lượng 5), chúng tôi có thể cung cấp trình so sánh nội tuyến (dựa trên độ dài của Chuỗi):

Sử dụng biểu thức lambda

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

Sử dụng tham chiếu Phương thức

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

Sau đó, chúng ta có thể sử dụng bất kỳ trong số họ như:

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

Điều này sẽ in:

Apple
PineApple
Custard Apple

Để đảo ngược thứ tự (để thay đổi nó thành hàng đợi ưu tiên tối đa) chỉ cần thay đổi thứ tự trong bộ so sánh nội tuyến hoặc sử dụng reversednhư:

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

Chúng tôi cũng có thể sử dụng Collections.reverseOrder:

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

Vì vậy, chúng ta có thể thấy rằng Collections.reverseOrderquá tải để có bộ so sánh có thể hữu ích cho các đối tượng tùy chỉnh. Thực reversedtế sử dụng Collections.reverseOrder:

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

cung cấp () so với thêm ()

Theo tài liệu

Phương thức chào hàng chèn một phần tử nếu có thể, nếu không trả về false. Điều này khác với phương thức Collection.add, có thể không thêm một phần tử chỉ bằng cách ném một ngoại lệ không được kiểm tra. Phương thức ưu đãi được thiết kế để sử dụng khi thất bại là một điều bình thường, thay vì xảy ra đặc biệt, ví dụ, trong các hàng đợi có công suất cố định (hoặc "giới hạn").

Khi sử dụng hàng đợi bị giới hạn dung lượng, Offer () thường thích hợp hơn để thêm (), điều này có thể không chèn một phần tử chỉ bằng cách ném một ngoại lệ. Và PriorityQueue là một hàng đợi ưu tiên không giới hạn dựa trên một đống ưu tiên.


Tôi đang giả sử 5chỉ ra khả năng bắt đầu của hàng đợi?
Neil

1
@Neil Vâng, tôi đã làm cho nó rõ ràng hơn trong câu trả lời ngay bây giờ :)
akhil_mittal

1
Phiên bản thứ 8 của Java là điều tốt nhất từng xảy ra với ngôn ngữ
GabrielBB

1
Giải thích rất hay với ví dụ sáng suốt.
Vishwa Ratna

24

Chỉ cần chuyển thích hợp Comparatorcho các nhà xây dựng :

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

Sự khác biệt duy nhất giữa offeraddlà giao diện họ thuộc về. offerthuộc về Queue<E>, trong khi addban đầu được nhìn thấy trong Collection<E>giao diện. Ngoài ra, cả hai phương thức đều thực hiện chính xác cùng một điều - chèn phần tử đã chỉ định vào hàng đợi ưu tiên.


7
Cụ thể, add () ném một ngoại lệ nếu các hạn chế về dung lượng ngăn không cho mục được thêm vào hàng đợi trong khi ưu đãi trả về sai. Vì PriorityQueues không có công suất tối đa, sự khác biệt là moot.
James

Đó là sự phân biệt rất rõ ràng giữa add () và Offer () .. Và add () là cần thiết để được thực hiện bằng mọi cách!
WhiteHat

19

từ API hàng đợi :

Phương thức chào hàng chèn một phần tử nếu có thể, nếu không trả về false. Điều này khác với phương thức Collection.add, có thể không thêm một phần tử chỉ bằng cách ném một ngoại lệ không được kiểm tra. Phương thức ưu đãi được thiết kế để sử dụng khi thất bại là một điều bình thường, thay vì xảy ra đặc biệt, ví dụ, trong các hàng đợi có công suất cố định (hoặc "giới hạn").


12

không khác nhau, như khai báo trong javadoc:

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

6

Chỉ cần trả lời câu hỏi add()vs offer()(vì câu hỏi còn lại được trả lời hoàn hảo, và điều này có thể không):

Theo JavaDoc trên Queue giao diện , "Phương thức chào hàng sẽ chèn một phần tử nếu có thể, nếu không thì trả về false. Điều này khác với phương thức Collection.add, có thể không thêm một phần tử chỉ bằng cách ném một ngoại lệ không được kiểm tra. Phương thức chào hàng được thiết kế cho sử dụng khi thất bại là một điều bình thường, thay vì xảy ra đặc biệt, ví dụ, trong các hàng đợi có công suất cố định (hoặc "giới hạn"). "

Điều đó có nghĩa là nếu bạn có thể thêm phần tử (luôn luôn là trường hợp trong PriorityQueue), chúng hoạt động chính xác như nhau. Nhưng nếu bạn không thể thêm phần tử, offer()sẽ mang lại cho bạn một kết quả tốt và đẹp false, đồng thời add()ném ra một ngoại lệ khó kiểm soát mà bạn không muốn trong mã của mình. Nếu không thêm nghĩa là mã đang hoạt động như dự định và / hoặc đó là thứ bạn sẽ kiểm tra bình thường, hãy sử dụng offer(). Nếu không thêm nghĩa là một cái gì đó bị hỏng, hãy sử dụng add()và xử lý ngoại lệ kết quả được ném theo thông số kỹ thuật của giao diện Bộ sưu tập .

Cả hai đều được triển khai theo cách này để hoàn thành hợp đồng trên giao diện Hàng đợi chỉ định offer()không thành công bằng cách trả về false( phương thức được ưu tiên trong hàng đợi giới hạn dung lượng ) và cũng duy trì hợp đồng trên giao diện Bộ sưu tập luôn chỉ định add()thất bại bằng cách ném ngoại lệ .

Dù sao, hy vọng rằng làm rõ ít nhất là một phần của câu hỏi.


6

Ở đây, chúng ta có thể định nghĩa bộ so sánh do người dùng định nghĩa:

Mã dưới đây:

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

Đầu ra:

   india                                               
   pakistan                                         
   bangladesh

Sự khác biệt giữa cung cấp và thêm phương thức: liên kết


1
nếu chúng bằng nhau
nycynik

4

Vượt qua nó a Comparator. Điền vào loại bạn muốn thay choT

Sử dụng lambdas (Java 8+):

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

Cách cổ điển, sử dụng lớp ẩn danh:

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

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

});

Để sắp xếp theo thứ tự ngược lại, chỉ cần trao đổi e1, e2.


3

Tôi cũng đã tự hỏi về thứ tự in. Hãy xem xét trường hợp này, ví dụ:

Đối với hàng đợi ưu tiên:

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

Mã này:

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

có thể in khác với:

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

Tôi đã tìm thấy câu trả lời từ một cuộc thảo luận trên một diễn đàn khác , nơi một người dùng nói, "các phương thức Offer () / add () chỉ chèn phần tử vào hàng đợi. Nếu bạn muốn một thứ tự dự đoán, bạn nên sử dụng peek / poll trả lại đầu của hàng đợi. "


3

Là một thay thế cho việc sử dụng Comparator, bạn cũng có thể có các lớp học mà bạn đang sử dụng trong bạn PriorityQueue thực hiệnComparable (và tương ứng ghi đè lên các compareTophương pháp).

Lưu ý rằng nói chung tốt nhất là chỉ sử dụng Comparablethay vì Comparatornếu thứ tự đó là thứ tự trực quan của đối tượng - ví dụ, nếu bạn có trường hợp sử dụng để sắp xếp Personcác đối tượng theo độ tuổi, có lẽ tốt nhất chỉ nên sử dụng Comparatorthay thế.

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

Đầu ra:

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

1

Hàng đợi ưu tiên có một số ưu tiên được gán cho từng thành phần, Phần tử có mức ưu tiên cao nhất xuất hiện ở Top Of Queue. Bây giờ, nó phụ thuộc vào bạn như thế nào bạn muốn ưu tiên được gán cho từng yếu tố. Nếu bạn không, Java sẽ làm theo cách mặc định. Phần tử có giá trị nhỏ nhất được gán mức ưu tiên cao nhất và do đó được loại bỏ khỏi hàng đợi trước. Nếu có một số yếu tố có cùng mức ưu tiên cao nhất, cà vạt bị phá vỡ tùy ý. Bạn cũng có thể chỉ định một đơn đặt hàng bằng Trình so sánh trong hàm tạo PriorityQueue(initialCapacity, comparator)

Mã ví dụ:

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

Đầu ra:

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

Khác, bạn cũng có thể xác định so sánh tùy chỉnh:

import java.util.Comparator;

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

1

Đây là ví dụ đơn giản mà bạn có thể sử dụng cho việc học ban đầu:

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

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.