文章收錄在公衆號:bigsai 關注持續分享乾貨和資源java
前言
事情還要從一個故事講起:
算法
對於上面那隻可愛的小狗狗不會,本篇即爲該教程,首先,我要告訴這隻可愛的小狗狗,這種問題你要使用的數據結構爲優先隊列,每次操做的時間複雜度爲O(logn)
,而整個過程的時間複雜度爲O(nlogn)
.api
對於本片的設計與實現和堆排序可能有些類似,由於他們都藉助堆來實現算法和數據結構,下面詳細介紹優先隊列的設計與實現。數組
堆
而堆就是一類特殊的數據結構的統稱。堆一般是一個能夠被看作一棵樹(徹底)的數組對象。且老是知足如下規則:微信
堆老是一棵徹底二叉樹數據結構
每一個節點老是大於(或小於)它的孩子節點。函數
對於徹底二叉樹,我想你們都能明白,就是最底層葉子節點要嚴格按照從左向右來。
學習
堆有大根堆和小根堆,若是是全部父節點都大於子節點的時候,那麼這就是個大根堆,反之則爲小根堆,如下就是一個大根堆:
測試
最後須要注意的是咱們並非用鏈式去儲存這個二叉樹而是用數組去存儲這個樹,雖然鏈式的使用場景可能更多一些,可是在徹底二叉樹的狀況下空間使用率較好沒有斜樹的出現。而且在操做的時候能夠直接經過編號找到位置進行交換。this
優先隊列
如何理解優先隊列,咱們先從一段對話提及:
優先隊列,它是一個隊列。而普通的隊列聽從先進先出的規則。而優先隊列遵循一個排序的規則:每次拋出自定義排序最大(小)的,默認的狀況是拋出最小的,本篇也就從最基本的原理進行分析。
而且它的用法隊列仍是同樣的,,因此咱們在設計這個類的時候api方面要與隊列的api一致。
咱們主要實現add、poll、和peek方法,而且會着重於算法的實現而不太着重一些細節的實現。
雖然優先隊列和堆排序利用堆結構特性的流程有一些類似,可是二者其實仍是有些操做上的區別的:
堆排序 :
剛開始是一個雜亂無章的序列,因此須要將雜亂的序列(樹)經過一個方法變成一個合法的堆。
轉成一個堆以後須要刪除n次每次刪除完都要從新調整這個堆。沒有插入操做。
優先隊列:
隊列(堆)剛開始的內容爲空,每次增長一個元素時須要及時調整堆。每次刪除也要及時調整堆,增長和刪除每次都只是一個元素。
可是優先隊列的具體操做流程是如何的呢?咱們具體分析其插入和刪除的流程。
插入add流程(小根堆爲例):
正常處理完的優先隊列內的數據知足一個堆的結構,因此就是插入在堆中。
堆是一棵徹底二叉樹,因此在插入初始,插入到最後一個位置不影響其餘結構。
節點和父節點比較大小(父節點索引爲其二分之一)。若是該節點比父節點更小,則交換數據,一直到不能交換爲止,這個過程不用擔憂不合法,由於父節點更小的話更知足比孩子節點更小。
刪除pop流程(小根堆爲例):
pop刪除操做取優先隊列內最小的元素,而這個元素確定就是堆頂元素了,取完以後,這個堆的其餘部分仍是知足堆的結構可是缺乏堆頂。
爲了避免影響整個結構,咱們將末尾的那個元素移到堆頂,此時堆須要調整使其知足堆的性質條件。
交換的這個節點和左右孩子進行比較,若是須要交換則交換,交換後再次考慮交換子節點是否須要交換,一直到不交換爲止。最壞狀況是交換到根節點,這個複雜度每次爲O(logn).
代碼實現
我想到這裏,優先隊列的內部流程思想你已經掌握了,可是懂優先隊列原理和手寫優先隊列是兩個概念,爲了更深刻的學習優先隊列,在這裏就帶你手寫一個簡易型的優先隊列。
在代碼的具體實現方面,最主要的就是pop()和add()兩個函數了。在pop()函數具體實現的時候,將最後一個元素移到堆頭考慮和其餘孩子節點交換的時候,用while進行操做的時候計算孩子下標的時候要確保不越界。咱們用的是數組存儲數據,優先隊列的長度不必定等於這個數組的長度。
而在實現add()函數的時候,這裏簡單的考慮了一下擴容。
具體實現的代碼爲:
import java.util.Arrays;
public class priQueue {
private int size;//優先隊列的大小
private int capacity;//數組的容量
private int value[];//儲存的值
public priQueue() {
this.capacity = 10;
this.value = new int[capacity];
this.size=0;
}
public priQueue(int capacity) {
this.capacity = capacity;
this.value = new int[capacity];
this.size=0;
}
/**
* 插入元素
* @param number
*/
public void add(int number) {
if(size==capacity)//擴容
{
capacity*=1.5;
value= Arrays.copyOf(value,capacity);
}
value[size++]=number;//先加到末尾
int index=size-1;
while (index>=1) {//進行交換
if(value[index]<value[index/2]) {
swap(index,index/2,value);
index=index/2;
}
else//不須要交換即中止
break;
}
}
public int peek() {
return value[0];
}
/**
* 拋出隊頭
* @return
*/
public int pop() {
int val=value[0];//呆返回數據額
value[0]=value[--size];//將最後一個元素賦值在堆頭
int index=0,leftChild=0,rightChild=0;
while (true)
{
leftChild=index*2+1;
rightChild=index*2+2;
if(leftChild>=size)//左孩子必須知足在條件內
break;
else if(rightChild<size&&value[rightChild]<value[index]&&value[rightChild]<value[leftChild])
{//右孩子更小
swap(index,rightChild,value);
index=rightChild;
}
else if(value[leftChild]<value[index])
{//左孩子更小
swap(index,leftChild,value);
index=leftChild;
}
else //不須要 它本身最小
break;
}
return val;
}
//交換兩個元素
public void swap(int i,int j,int arr[]) {
int team=arr[i];
arr[i]=arr[j];
arr[j]=team;
}
public int size() {
return size;
}
}
寫個類測試一下看看:
結語
本次優先隊列介紹就到這裏啦,感受不錯記得點贊或一鍵三連哦,建議和堆排序一塊兒看和學習效果更佳,要可以手寫代碼。我的公衆號:bigsai
回覆 bigsai 更多精彩和資源與你分享。
本文分享自微信公衆號 - bigsai(bigsai)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。