1. 程式人生 > >Java優先佇列(PriorityQueue)

Java優先佇列(PriorityQueue)

參考
http://www.importnew.com/6932.html
https://www.cnblogs.com/gnivor/p/4841191.html

我們知道佇列是遵循先進先出(First-In-First-Out)模式的,但有些時候需要在佇列中基於優先順序處理物件。舉個例子,比方說我們有一個每日交易時段生成股票報告的應用程式,需要處理大量資料並且花費很多處理時間。客戶向這個應用程式傳送請求時,實際上就進入了佇列。我們需要首先處理優先客戶再處理普通使用者。在這種情況下,Java的PriorityQueue(優先佇列)會很有幫助。

PriorityQueue類在Java1.5中引入並作為 Java Collections Framework 的一部分。PriorityQueue是基於優先堆的一個無界佇列,這個優先佇列中的元素可以預設自然排序或者通過提供的Comparator(比較器)在佇列例項化的時排序。(不指定Comparator時預設為最小堆)

優先佇列不允許空值,而且不支援non-comparable(不可比較)的物件,比如使用者自定義的類。優先佇列要求使用Java Comparable和Comparator介面給物件排序,並且在排序時會按照優先順序處理其中的元素。

優先佇列的頭是基於自然排序或者Comparator排序的最小元素。如果有多個物件擁有同樣的排序,那麼就可能隨機地取其中任意一個。當我們獲取佇列時,返回佇列的頭物件。

優先佇列的大小是不受限制的,但在建立時可以指定初始大小。當我們向優先佇列增加元素的時候,佇列大小會自動增加。

PriorityQueue是非執行緒安全的,所以Java提供了PriorityBlockingQueue(實現BlockingQueue介面)用於Java多執行緒環境。

我們有一個使用者類Customer,它沒有提供任何型別的排序。當我們用它建立優先佇列時,應該為其提供一個比較器物件。

Customer.java

package com.journaldev.collections;
 
public class Customer {
 
    private int id;
    private String name;
 
    public Customer(int i, String n){
        this.id=i;
        this.name=n;
    }
 
    public int getId() {
        return id;
    }
 
    public String getName() {
        return name;
    }
 
}

我們使用Java隨機數生成隨機使用者物件。對於自然排序,我們使用Integer物件,這也是一個封裝過的Java物件。

下面是最終的測試程式碼,展示如何使用PriorityQueue:

PriorityQueueExample.java

package com.journaldev.collections;
 
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;
 
public class PriorityQueueExample {
 
    public static void main(String[] args) {
 
        //優先佇列自然排序示例
        Queue<Integer> integerPriorityQueue = new PriorityQueue<>(7);
        Random rand = new Random();
        for(int i=0;i<7;i++){
            integerPriorityQueue.add(new Integer(rand.nextInt(100)));
        }
        for(int i=0;i<7;i++){
            Integer in = integerPriorityQueue.poll();
            System.out.println("Processing Integer:"+in);
        }
 
        //優先佇列使用示例
        Queue<Customer> customerPriorityQueue = new PriorityQueue<>(7, idComparator);
        addDataToQueue(customerPriorityQueue);
 
        pollDataFromQueue(customerPriorityQueue);
 
    }
 
    //匿名Comparator實現
    public static Comparator<Customer> idComparator = new Comparator<Customer>(){
 
        @Override
        public int compare(Customer c1, Customer c2) {
            return (int) (c1.getId() - c2.getId());
        }
    };
 
    //用於往佇列增加資料的通用方法
    private static void addDataToQueue(Queue<Customer> customerPriorityQueue) {
        Random rand = new Random();
        for(int i=0; i<7; i++){
            int id = rand.nextInt(100);
            customerPriorityQueue.add(new Customer(id, "Pankaj "+id));
        }
    }
 
    //用於從佇列取資料的通用方法
    private static void pollDataFromQueue(Queue<Customer> customerPriorityQueue) {
        while(true){
            Customer cust = customerPriorityQueue.poll();
            if(cust == null) break;
            System.out.println("Processing Customer with ID="+cust.getId());
        }
    }
 
}

注意我用實現了Comparator介面的Java匿名類,並且實現了基於id的比較器。

當我執行以上測試程式時,我得到以下輸出:

Processing Integer:9
Processing Integer:16
Processing Integer:18
Processing Integer:25
Processing Integer:33
Processing Integer:75
Processing Integer:77
Processing Customer with ID=6
Processing Customer with ID=20
Processing Customer with ID=24
Processing Customer with ID=28
Processing Customer with ID=29
Processing Customer with ID=82
Processing Customer with ID=96

從輸出結果可以清楚的看到,最小的元素在佇列的頭部因而最先被取出。如果不實現Comparator,在建立customerPriorityQueue時會丟擲ClassCastException。

Exception in thread "main" java.lang.ClassCastException: com.journaldev.collections.Customer cannot be cast to java.lang.Comparable
    at java.util.PriorityQueue.siftUpComparable(PriorityQueue.java:633)
    at java.util.PriorityQueue.siftUp(PriorityQueue.java:629)
    at java.util.PriorityQueue.offer(PriorityQueue.java:329)
    at java.util.PriorityQueue.add(PriorityQueue.java:306)
    at com.journaldev.collections.PriorityQueueExample.addDataToQueue(PriorityQueueExample.java:45)
    at com.journaldev.collections.PriorityQueueExample.main(PriorityQueueExample.java:25)

總結:

注意1:該佇列是用陣列實現,但是陣列大小可以動態增加,容量無限。

注意2:佇列的實現不是同步的。不是執行緒安全的。如果多個執行緒中的任意執行緒從結構上修改了列表, 則這些執行緒不應同時訪問 PriorityQueue例項。保證執行緒安全可以使用PriorityBlockingQueue 類。

注意3:不允許使用 null 元素。

注意4:插入方法(offer()、poll()、remove() 、add() 方法)時間複雜度為O(log(n)) ;
remove(Object) 和 contains(Object) 時間複雜度為O(n);
檢索方法(peek、element 和 size)時間複雜度為常量。

注意5:方法iterator()中提供的迭代器並不保證以有序的方式遍歷優先順序佇列中的元素。(原因可參考PriorityQueue的內部實現)
如果需要按順序遍歷,可用Arrays.sort(pq.toArray())。

注意6:可以在建構函式中指定如何排序。如:
PriorityQueue()
使用預設的初始容量(11)建立一個 PriorityQueue,並根據其自然順序來排序其元素(使用 Comparable)。
PriorityQueue(int initialCapacity)
使用指定的初始容量建立一個 PriorityQueue,並根據其自然順序來排序其元素(使用 Comparable)。
PriorityQueue(int initialCapacity, Comparator<? super E> comparator)
使用指定的初始容量建立一個 PriorityQueue,並根據指定的比較器comparator來排序其元素。

注意7:此類及其迭代器實現了 Collection 和 Iterator 介面的所有可選 方法。

PriorityQueue對元素採用的是堆排序,頭是按指定排序方式的最小元素。堆排序只能保證根是最大(最小),整個堆並不是有序的。
方法iterator()中提供的迭代器可能只是對整個陣列的依次遍歷。也就只能保證陣列的第一個元素是最小的。

利用 優先佇列 求陣列中前k個最小的數字,時間複雜度:O(nlogk)
更多解法相見
https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&rp=2&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

/*
    用最大堆儲存這k個數,每次只和堆頂比,如果比堆頂小,刪除堆頂,新數入堆。
    最大堆 時間複雜度O(nlogk)
     */
    public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        //這裡不要忘了判斷 k>input.length
        if (input == null || input.length == 0 || k <= 0 || k > input.length) {
            return res;
        }

        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        for (int i = 0; i < input.length; i++) {
            if (maxHeap.size() != k) {
                maxHeap.offer(input[i]);
            } else if (input[i] < maxHeap.peek()) {
                maxHeap.poll();
                maxHeap.offer(input[i]);
            }
        }
        for (int num : maxHeap) {
            res.add(num);
        }
        return res;
    }

以上就是優先佇列的全部內容,如果你喜歡這篇文章,請參與分享或者評論。