一、PriorityQueue 的使用
- 概念
隊列是一種先進先出(FIFO)的數據結構,但有時候,數據可能帶有優先級,出隊列時,可能需要優先級高的元素先出隊列,在這種情況下,使用隊列顯然不合適,比如:在手機上玩遊戲的時候,如果有來電,那麼系統應該優先處理打進來的電話。
- 常用接口介紹
1、JDK:
提供了兩種類型的優先級隊列:①PriorityQueue是線程不安全的,②PriorityBlockingQueue是線程安全的。
2、PriorityQueue使用前,應先導入包
import java.util.PriorityQueue;
【注意】
- 插入的元素不能爲null,且元素之間必須要能夠進行比較;
- 插入、刪除------在插入和刪除元素期間優先級隊列中的元素會自定進行調整(不論怎麼調整,0號位置的元素始終是最小的);插入和刪除的時間複雜度: O(logN)。
- 底層結構—堆(暫時不用管什麼是堆) 也可以通過某種方式使首元素始終最大
3、PriorityQueue包含的接口:
-
構造方式:
-
PriorityQueue()空的優先級隊列:默認的容量爲11 ;
PriorityQueue q1 = new PriorityQueue<>();
- PriorityQueue(capacity):指定初始化容量大小 (capacity不能小於1,如果小於1會拋異常 )
PriorityQueue p2 = new PriorityQueue<>(20);
- PriorityQueue(集合對象):用一個集合中的元素來構造優先級隊列
ArrayList<Integer> list = new ArrayList<>();
list.add(4);
list.add(3);
list.add(2);
list.add(1);
//用ArrayList對象來構造一個優先級隊列的對象
//q3中已經包含了三個元素
PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
- 常用接口:
- offer(x) :插入元素---->O(logN)
- poll():刪除元素
- size():獲取有效元素個數
- isEmpty(): 檢測是否爲空
- clear():將優先級隊列中的元素清空
public static void TestPriorityQueue() {
int[] arr = {4, 1, 9, 2, 8, 0, 7, 3, 6, 5};
// 一般在創建優先級隊列對象時,如果知道元素個數,建議就直接將底層容量給好
// 否則在插入時需要不多的擴容
// 擴容機制:開闢更大的空間,拷貝元素,這樣效率會比較低
PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
for (int e : arr) {
q.offer(e);
}
System.out.println(q.size()); // 打印優先級隊列中有效元素個數
System.out.println(q.peek()); // 獲取優先級最高的元素
// 從優先級隊列中刪除兩個元素之和,再次獲取優先級最高的元素
q.poll();
q.poll();
System.out.println(q.size()); // 打印優先級隊列中有效元素個數
System.out.println(q.peek()); // 獲取優先級最高的元素
q.offer(0);
System.out.println(q.peek()); // 獲取優先級最高的元素
// 將優先級隊列中的有效元素刪除掉,檢測其是否爲空
q.clear();
if (q.isEmpty()) {
System.out.println("優先級隊列已經爲空!!!");
} else {
System.out.println("優先級隊列不爲空");
}
}
二、堆的概念及實現
-
概念
有一個集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉樹的順序存儲方式存儲 在一個一維數組中,並滿足:Ki <= K2i+1 且 Ki<= K2i+2,則稱爲 小堆(或大堆)。將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆。
如圖所示爲小堆,根節點的值小於左右子樹的值,反之則爲大堆。 -
存儲方式
堆是一棵完全二叉樹,因此可以按照層序的規則採用順序的方式存儲。對於非完全二叉樹,則不適合使用順序方式進行存儲,因爲爲了能夠還原二叉樹,空間中必須要存儲空節點,就會導致空間利用率比較低。
對於具有n個結點的完全二叉樹,按照從上到下、從左到右的順序對節點從0開始編號,假設i爲節點在數組中的下標,則有:
- 若i>0,雙親序號: (i-1)/2; i=0, i爲根節點編號,無雙親節點.
- 若2i+1<n, 左孩子序號: 2i+1,否則無左孩子
- 若2i+2<n, 右孩子序號: 2i+2,否則無右孩子
【堆的創建】
- 向下調整
如圖, 對於集合{ 27,15,19,18,28,34,65,49,25,37 },根節點的左右子樹已經完全滿足堆的性質,因此只需將根節點向下調整好即可。
過程(以小堆爲例):
1、parent爲本次調整節點的下標,調整以parent爲根的二叉樹;
(調整之前,要保證parent的左右子堆已經滿足小堆的性質)
2、檢測parent是否滿足小堆性質:將parent與其孩子的比較
- 滿足:以parent爲根節點的二叉樹已經是小堆
- 不滿足:說明parent比其孩子大,將parent與其較小的孩子進行交換,交換後,parent的向下移動,
可能導致其子樹不滿足小堆性質,繼續調整;
private void shiftDown(int parent){
//使用child標記parent較小的孩子
//默認情況下,先標記左孩子(完全二叉樹:可能有左孩子沒有右孩子)
int child = parent * 2 + 1;
while (child<size){
//找parent中較小的孩子
if(res[child+1] < res[child]){//如果右孩子比左孩子小
child+=1;
}
//比較parent與child的大小
if(res[parent]>res[child]){
swap(parent,child);
//調整後,如果子樹不滿足小堆的性質,繼續調整
parent=child;
child=parent*2+1;
}else{
return;//以parent爲根的二叉樹已經滿足小堆性質
}
}
}
private void swap(int parent,int child){
int temp = res[parent];
res[parent]=res[child];
res[child]=temp;
}
- 堆的創建
對於根節點的左右子樹不滿足堆的特性,我們需要不斷地進行調整:
public static void createHeap(int[] array) {
//找到第一個非葉子節點(根) child=parent*2+1 ----- parent = (child-1)/2
int lastLeaf= (size-1)-1>>1;
//調整很多個根節點 從下往上找葉子節點
for (int root = lastLeaf; root >=0 ; root--) {
shiftDown(root);//每一個根節點都要向下調整
}
}
- 堆的插入
堆的插入總共需要兩個步驟:
- 先將元素放入到底層空間中(注意:空間不夠時需要擴容)
- 將最後新插入的節點向上調整,直到滿足堆的性質
private boolean offer(int x){
grow();//擴容
//1、將元素尾插到數組中
res[size++] = x;
//2、檢測新元素插入後是否破壞小堆的性質
shiftUp(size-1);//向上調整
return true;
}
private void shiftUp(int child) {
int parent = (child - 1) >> 1;
if (res[parent] > res[child]) {
swap(parent, child);
//調整後,可能導致不滿足堆的性質 繼續向上調整
child = parent;
parent = (child - 1) >> 1;
} else {
return;
}
}
private void swap(int parent,int child){
int temp = res[parent];
res[parent]=res[child];
res[child]=temp;
}
private void grow(){
if(size>= res.length){
int oldCapacity = res.length;
int newCapacity = oldCapacity+ ((oldCapacity<64)?(oldCapacity + 2):(oldCapacity >> 1));
}
}
- 堆的刪除
堆的刪除一定刪除的是堆頂元素:
- 將堆頂元素對堆中最後一個元素交換;
- 將堆中有效數據個數減少一個;
- 對堆頂元素進行向下調整;
private int poll(){
int a = res[0];
swap(0,size-1);//0號元素和新插入元素交換
size--;
shiftDown(0);//向下調整
return a;
}