本篇是數據結構與算法的第三篇,本篇咱們未來了解一下知識點:java
隊列一樣是一種特殊的線性表,其插入和刪除的操做分別在表的兩端進行,隊列的特色就是先進先出(First In First Out)。咱們把向隊列中插入元素的過程稱爲入隊(Enqueue),刪除元素的過程稱爲出隊(Dequeue)並把容許入隊的一端稱爲隊尾,容許出的的一端稱爲隊頭,沒有任何元素的隊列則稱爲空隊。其通常結構以下:算法
關於隊列的操做,咱們這裏主要實現入隊,出隊,判斷空隊列和清空隊列等操做,聲明隊列接口Queue(隊列抽象數據類型)以下:數組
1 /** 2 * 隊列抽象數據類型 3 */ 4 public interface Queue<T> { 5 6 /** 7 * 返回隊列長度 8 * @return 9 */ 10 int size(); 11 12 /** 13 * 判斷隊列是否爲空 14 * @return 15 */ 16 boolean isEmpty(); 17 18 /** 19 * data 入隊,添加成功返回true,不然返回false,可擴容 20 * @param data 21 * @return 22 */ 23 boolean add(T data); 24 25 /** 26 * offer 方法可插入一個元素,這與add 方法不一樣, 27 * 該方法只能經過拋出未經檢查的異常使添加元素失敗。 28 * 而不是出現異常的狀況,例如在容量固定(有界)的隊列中 29 * NullPointerException:data==null時拋出 30 * @param data 31 * @return 32 */ 33 boolean offer(T data); 34 35 /** 36 * 返回隊頭元素,不執行刪除操做,若隊列爲空,返回null 37 * @return 38 */ 39 T peek(); 40 41 /** 42 * 返回隊頭元素,不執行刪除操做,若隊列爲空,拋出異常:NoSuchElementException 43 * @return 44 */ 45 T element(); 46 47 /** 48 * 出隊,執行刪除操做,返回隊頭元素,若隊列爲空,返回null 49 * @return 50 */ 51 T poll(); 52 53 /** 54 * 出隊,執行刪除操做,若隊列爲空,拋出異常:NoSuchElementException 55 * @return 56 */ 57 T remove(); 58 59 /** 60 * 清空隊列 61 */ 62 void clearQueue(); 63 }
下面咱們就來分別實現順序隊列和鏈式隊列數據結構
關於順序隊列(底層都是利用數組做爲容器)的實現,咱們將採用順序循環隊列的結構來實現,在給出實現方案前先來分析一下爲何不直接使用順序表做爲底層容器來實現。實際上採用順序表實現隊列時,入隊操做直接執行順序表尾部插入操做,其時間複雜度爲O(1),出隊操做直接執行順序表頭部刪除操做,其時間複雜度爲O(n),主要用於移動元素,效率低,既然如此,咱們就把出隊的時間複雜度降爲O(1)便可,爲此在順序表中添加一個頭指向下標front和尾指向下標,出隊和入隊時只要改變front、rear的下標指向取值便可,此時無需移動元素,所以出隊的時間複雜度也就變爲O(1)。其過程以下圖所示異步
以上是添加front和rear下標記錄的順序表插入過程編輯器
從圖的演示過程,(a)操做時,是空隊列此時front和rear都爲-1,同時能夠發現雖然咱們經過給順序表添加front和rear變量記錄下標後使用得出隊操做的時間複雜度降爲O(1),可是卻出現了另一個嚴重的問題,那就是空間浪費,從圖中的(d)和(e)操做能夠發現,20和30出隊後,遺留下來的空間並無被從新利用,反而是空着,因此致使執行(f)操做時,出現隊列已滿的假現象,這種假現象咱們稱之爲假溢出,之因此出現這樣假溢出的現象是由於順序表隊列的存儲單元沒有重複利用機制,而解決該問題的最合適的方式就是將順序隊列設計爲循環結構,接下來咱們就經過循環順序表來實現順序隊列。
順序循環隊列就是將順序隊列設計爲在邏輯結構上收尾相接的循環結構,這樣咱們就能夠重複利用存儲單元,其過程以下所示:ide
簡單分析一下:
其中採用循環結構的順序表,能夠循環利用存儲單元,所以有以下計算關係(其中size爲隊列長度):測試
//其中front、rear的下標的取值範圍是0~size-1,不會形成假溢出。 front=(front+1)%size;//隊頭下標 rear=(rear+1)%size;
front爲隊頭元素的下標,rear則指向下一個入隊元素的下標this
當front=rear時,咱們約定隊列爲空。idea
出隊操做改變front下標指向,入隊操做改變rear下標指向,size表明隊列容量。
約定隊列滿的條件爲front=(rear+1)%size,注意此時隊列中仍有一個空的位置,此處留一個空位主要用於避免與隊列空的條件front=rear相同。
隊列內部的數組可擴容,並按照原來隊列的次序複製元素數組
瞭解了隊列的實現規則後,咱們重點分析一下入隊add方法和出隊poll方法,其中入隊add方法實現以下:
/** * data 入隊,添加成功返回true,不然返回false,可擴容 * @param data * @return */ @Override public boolean add(T data) { //判斷是否滿隊 if (this.front==(this.rear+1)%this.elementData.length){ ensureCapacity(elementData.length*2+1); } //添加data elementData[this.rear]=data; //更新rear指向下一個空元素的位置 this.rear=(this.rear+1)%elementData.length; size++; return true; }
在add方法中咱們先經過this.front==(this.rear+1)%this.elementData.length
判斷隊列是否滿,在前面咱們約定過隊列滿的條件爲front=(rear+1)%size,若是隊列滿,則先經過ensureCapacity(elementData.length*2+1)
擴容,該方法實現以下:
1 /** 2 * 擴容的方法 3 * @param capacity 4 */ 5 public void ensureCapacity(int capacity) { 6 //若是須要拓展的容量比如今數組的容量還小,則無需擴容 7 if (capacity<size) 8 return; 9 10 T[] old = elementData; 11 elementData= (T[]) new Object[capacity]; 12 int j=0; 13 //複製元素 14 for (int i=this.front; i!=this.rear ; i=(i+1)%old.length) { 15 elementData[j++] = old[i]; 16 } 17 //恢復front,rear指向 18 this.front=0; 19 this.rear=j; 20 }
這個方法比較簡單,主要建立一個新容量的數組,並把舊數組中的元素複製到新的數組中,這裏惟一的要注意的是,判斷久數組是否複製完成的條件爲i!=this.rear
,同時循環的自增條件爲i=(i+1)%old.length
。擴容後直接經過rear添加新元素,最後更新rear指向下一個入隊新元素。對於出隊操做poll的實現以下:
1 /** 2 * 出隊,執行刪除操做,返回隊頭元素,若隊列爲空,返回null 3 * @return 4 */ 5 @Override 6 public T poll() { 7 T temp=this.elementData[this.front]; 8 this.front=(this.front+1)%this.elementData.length; 9 size--; 10 return temp; 11 }
出隊操做相對簡單些,直接存儲要刪除元素的數據,並更新隊頭front的值,最後返回刪除元素的數據。ok~,關於循環結構的順序隊列,咱們就分析到此,最後給出循環順序隊列的實現源碼,其餘方法比較簡單,註釋也很清楚,就不過多分析了:
1 import java.io.Serializable; 2 import java.util.NoSuchElementException; 3 4 /** 5 * 順序隊列的實現 6 */ 7 public class SeqQueue<T> implements Queue<T> ,Serializable { 8 9 10 private static final long serialVersionUID = -1664818681270068094L; 11 private static final int DEFAULT_SIZE = 10; 12 13 private T elementData[]; 14 15 private int front,rear; 16 17 private int size; 18 19 20 public SeqQueue(){ 21 elementData= (T[]) new Object[DEFAULT_SIZE]; 22 front=rear=0; 23 } 24 25 public SeqQueue(int capacity){ 26 elementData= (T[]) new Object[capacity]; 27 front=rear=0; 28 } 29 30 @Override 31 public int size() { 32 // LinkedList 33 return size; 34 } 35 36 @Override 37 public boolean isEmpty() { 38 return front==rear; 39 } 40 41 /** 42 * data 入隊,添加成功返回true,不然返回false,可擴容 43 * @param data 44 * @return 45 */ 46 @Override 47 public boolean add(T data) { 48 //判斷是否滿隊 49 if (this.front==(this.rear+1)%this.elementData.length){ 50 ensureCapacity(elementData.length*2+1); 51 } 52 //添加data 53 elementData[this.rear]=data; 54 //更新rear指向下一個空元素的位置 55 this.rear=(this.rear+1)%elementData.length; 56 size++; 57 return true; 58 } 59 60 /** 61 * offer 方法可插入一個元素,這與add 方法不一樣, 62 * 該方法只能經過拋出未經檢查的異常使添加元素失敗。 63 * 而不是出現異常的狀況,例如在容量固定(有界)的隊列中 64 * NullPointerException:data==null時拋出 65 * IllegalArgumentException:隊滿,使用該方法可使Queue的容量固定 66 * @param data 67 * @return 68 */ 69 @Override 70 public boolean offer(T data) { 71 if (data==null) 72 throw new NullPointerException("The data can\'t be null"); 73 //隊滿拋出異常 74 if (this.front==(this.rear+1)%this.elementData.length){ 75 throw new IllegalArgumentException("The capacity of SeqQueue has reached its maximum"); 76 } 77 78 //添加data 79 elementData[this.rear]=data; 80 //更新rear指向下一個空元素的位置 81 this.rear=(this.rear+1)%elementData.length; 82 size++; 83 84 return true; 85 } 86 87 /** 88 * 返回隊頭元素,不執行刪除操做,若隊列爲空,返回null 89 * @return 90 */ 91 @Override 92 public T peek() { 93 return elementData[front]; 94 } 95 96 /** 97 * 返回隊頭元素,不執行刪除操做,若隊列爲空,拋出異常:NoSuchElementException 98 * @return 99 */ 100 @Override 101 public T element() { 102 if(isEmpty()){ 103 throw new NoSuchElementException("The SeqQueue is empty"); 104 } 105 return peek(); 106 } 107 108 /** 109 * 出隊,執行刪除操做,返回隊頭元素,若隊列爲空,返回null 110 * @return 111 */ 112 @Override 113 public T poll() { 114 T temp=this.elementData[this.front]; 115 this.front=(this.front+1)%this.elementData.length; 116 size--; 117 return temp; 118 } 119 120 /** 121 * 出隊,執行刪除操做,若隊列爲空,拋出異常:NoSuchElementException 122 * @return 123 */ 124 @Override 125 public T remove() { 126 if (isEmpty()){ 127 throw new NoSuchElementException("The SeqQueue is empty"); 128 } 129 return poll(); 130 } 131 132 @Override 133 public void clearQueue() { 134 for (int i=this.front; i!=this.rear ; i=(i+1)%elementData.length) { 135 elementData[i] = null; 136 } 137 //復位 138 this.front=this.rear=0; 139 size=0; 140 } 141 142 /** 143 * 擴容的方法 144 * @param capacity 145 */ 146 public void ensureCapacity(int capacity) { 147 //若是須要拓展的容量比如今數組的容量還小,則無需擴容 148 if (capacity<size) 149 return; 150 151 T[] old = elementData; 152 elementData= (T[]) new Object[capacity]; 153 int j=0; 154 //複製元素 155 for (int i=this.front; i!=this.rear ; i=(i+1)%old.length) { 156 elementData[j++] = old[i]; 157 } 158 //恢復front,rear指向 159 this.front=0; 160 this.rear=j; 161 } 162 }
分析完順序隊列,咱們接着看看鏈式隊列的設計與實現,對於鏈式隊列,將使用帶頭指針front和尾指針rear的單鏈表實現,front直接指向隊頭的第一個元素,rear指向隊尾的最後一個元素,其結構以下:
之因此選擇單鏈表(帶頭尾指針)而不採用循環雙鏈表或者雙鏈表主要是雙鏈表的空間開銷(空間複雜度,多前繼指針)相對單鏈表來講大了很多,而單鏈表只要新增頭指針和尾指針就能夠輕鬆實現常數時間內(時間複雜度爲O(1))訪問頭尾結點。下面咱們來看看如何設計鏈式隊列:
以上述的圖爲例分別設置front和rear指向隊頭結點和隊尾結點,使用單鏈表的頭尾訪問時間複雜度爲O(1)。
設置初始化空隊列,使用front=rear=null,而且約定條件front==null&&rear==null
成立時,隊列爲空。
出隊操做時,若隊列不爲空獲取隊頭結點元素,並刪除隊頭結點元素,更新front指針的指向爲front=front.next
入隊操做時,使插入元素的結點在rear以後並更新rear指針指向新插入元素。
當第一個元素入隊或者最後一個元素出隊時,同時更新front指針和rear指針的指向。
這一系列過程以下圖所示:
ok~,關於鏈式隊列的設計都分析完,至於實現就比較簡單了,和以前分析過的單鏈表區別不大,所以這裏咱們直接給出實現代碼便可:
1 import com.zejian.structures.LinkedList.singleLinked.Node; 2 3 import java.io.Serializable; 4 import java.util.*; 5 6 /** 7 * 鏈式隊列的實現 8 */ 9 public class LinkedQueue<T> implements Queue<T> ,Serializable{ 10 private static final long serialVersionUID = 1406881264853111039L; 11 /** 12 * 指向隊頭和隊尾的結點 13 * front==null&&rear==null時,隊列爲空 14 */ 15 private Node<T> front,rear; 16 17 private int size; 18 /** 19 * 用於控制最大容量,默認128,offer方法使用 20 */ 21 private int maxSize=128; 22 23 public LinkedQueue(){ 24 //初始化隊列 25 this.front=this.rear=null; 26 } 27 28 @Override 29 public int size() { 30 return size; 31 } 32 33 public void setMaxSize(int maxSize){ 34 this.maxSize=maxSize; 35 } 36 37 @Override 38 public boolean isEmpty() { 39 return front==null&&rear==null; 40 } 41 42 /** 43 * data 入隊,添加成功返回true,不然返回false,可擴容 44 * @param data 45 * @return 46 */ 47 @Override 48 public boolean add(T data) { 49 Node<T> q=new Node<>(data,null); 50 if (this.front==null) {//空隊列插入 51 this.front = q; 52 } else {//非空隊列,尾部插入 53 this.rear.next=q; 54 } 55 this.rear=q; 56 size++; 57 return true; 58 } 59 60 /** 61 * offer 方法可插入一個元素,這與add 方法不一樣, 62 * 該方法只能經過拋出未經檢查的異常使添加元素失敗。 63 * 而不是出現異常的狀況,例如在容量固定(有界)的隊列中 64 * NullPointerException:data==null時拋出 65 * IllegalArgumentException:隊滿,使用該方法可使Queue的容量固定 66 * @param data 67 * @return 68 */ 69 @Override 70 public boolean offer(T data) { 71 if (data==null) 72 throw new NullPointerException("The data can\'t be null"); 73 if (size>=maxSize) 74 throw new IllegalArgumentException("The capacity of LinkedQueue has reached its maxSize:128"); 75 76 Node<T> q=new Node<>(data,null); 77 if (this.front==null) {//空隊列插入 78 this.front = q; 79 } else {//非空隊列,尾部插入 80 this.rear.next=q; 81 } 82 this.rear=q; 83 size++; 84 return false; 85 } 86 87 /** 88 * 返回隊頭元素,不執行刪除操做,若隊列爲空,返回null 89 * @return 90 */ 91 @Override 92 public T peek() { 93 return this.isEmpty()? null:this.front.data; 94 } 95 96 /** 97 * 返回隊頭元素,不執行刪除操做,若隊列爲空,拋出異常:NoSuchElementException 98 * @return 99 */ 100 @Override 101 public T element() { 102 if(isEmpty()){ 103 throw new NoSuchElementException("The LinkedQueue is empty"); 104 } 105 return this.front.data; 106 } 107 108 /** 109 * 出隊,執行刪除操做,返回隊頭元素,若隊列爲空,返回null 110 * @return 111 */ 112 @Override 113 public T poll() { 114 if (this.isEmpty()) 115 return null; 116 T x=this.front.data; 117 this.front=this.front.next; 118 if (this.front==null) 119 this.rear=null; 120 size--; 121 return x; 122 } 123 124 /** 125 * 出隊,執行刪除操做,若隊列爲空,拋出異常:NoSuchElementException 126 * @return 127 */ 128 @Override 129 public T remove() { 130 if (isEmpty()){ 131 throw new NoSuchElementException("The LinkedQueue is empty"); 132 } 133 T x=this.front.data; 134 this.front=this.front.next; 135 if (this.front==null) 136 this.rear=null; 137 size--; 138 return x; 139 } 140 141 @Override 142 public void clearQueue() { 143 this.front= this.rear=null; 144 size=0; 145 } 146 }
瞭解完循環順序隊列和鏈式隊列的實現後,咱們最後再來了解一個特殊的隊列,也就是優先隊列,在某些狀況下,有些應用系統要求不只須要按照「先來先服務」的原則進行,並且還需按照任務的重要或緊急程度進行排隊處理,此時就須要使用到優先隊列。好比在操做系統中進行進程調度管理,每一個進程都具有一個優先級值以表示進程的緊急程度,優先級高的進行先執行,同等級進程按照先進先出的原則排隊處理,此時操做系統使用的即是優先隊列管理和調度進程。
優先級隊列也是一種特殊的數據結構,隊列中的每一個元素都有一個優先級,若每次出隊的是具備最高優先級的元素,則稱爲降序優先級隊列(老是先刪除最大的元素)。若每次出隊的是值最小的元素,則稱爲升序優先級隊列(老是先刪除最小的元素),一般狀況下咱們所說的優先隊列,通常是指降序優先級隊列。關於優先隊列的實現,可使用有序數組或者有序鏈表,也可使用二叉樹(二叉堆)實現,這裏咱們僅給出有序鏈表的簡單實現方案。而二叉樹的實現,留着後面咱們分析完樹時再給出。好~,這裏使用以前分析過的MyLikedList做爲基底,實現一個排序的SortLinkedList繼承自MyLinkedList,這裏須要注意的是排序鏈表中的T類型必須是實現了Comparable接口的類型,在SortLinkedList中主要重寫添加的add方法,插入邏輯是,經過比較元素的大小加入,而非簡單下標或尾部插入,其實現以下:
1 import java.io.Serializable; 2 import java.util.Iterator; 3 import java.util.ListIterator; 4 5 /** 6 * 排序list的簡單實現 7 */ 8 public class SortMyLinkedList<T extends Comparable<? extends T>> extends MylinkeList<T> implements Serializable { 9 10 private static final long serialVersionUID = -4783131709270334156L; 11 12 @Override 13 public boolean add(T data) { 14 if(data==null) 15 throw new NullPointerException("data can\'t be null"); 16 17 Comparable cmp =data;//這裏須要轉一下類型,不然idea編輯器上檢驗不經過. 18 19 if(this.isEmpty() || cmp.compareTo(this.last.prev.data) > 0){ 20 return super.add(data);//直接尾部添加,last不帶數據的尾結點 21 } 22 23 Node<T> p=this.first.next; 24 //查找插入點 25 while (p!=null&&cmp.compareTo(p.data)>0) 26 p=p.next; 27 28 Node<T> q=new Node<>(p.prev,data,p); 29 p.prev.next=q; 30 p.prev=q; 31 32 size++; 33 //記錄修改 34 modCount++; 35 36 return true; 37 } 38 39 /** 40 * 不根據下標插入,只根據比較大小插入 41 * @param index 42 * @param data 43 */ 44 @Override 45 public void add(int index, T data) { 46 this.add(data); 47 } 48 49 50 /** 51 * 未實現 52 * @param index 53 * @return 54 */ 55 @Override 56 public ListIterator<T> listIterator(int index) { 57 return null; 58 } 59 60 /** 61 * 未實現 62 * @return 63 */ 64 @Override 65 public Iterator<T> iterator() { 66 return null; 67 } 68 69 //測試 70 public static void main(String[] args){ 71 SortMyLinkedList<Integer> list=new SortMyLinkedList<>(); 72 list.add(50); 73 list.add(40); 74 list.add(80); 75 list.add(20); 76 print(list); 77 } 78 79 public static void print(SortMyLinkedList mylinkeList){ 80 for (int i=0;i<mylinkeList.size();i++) { 81 System.out.println("i->"+mylinkeList.get(i)); 82 } 83 } 84 }
接着以SortMyLinkedList爲基底實現優先隊列PriorityQueue,實現源碼以下,實現比較簡單,感受沒啥好說的:
1 import com.zejian.structures.LinkedList.MyCollection.SortMyLinkedList; 2 3 import java.io.Serializable; 4 import java.util.NoSuchElementException; 5 6 /** 7 * 優先隊列的簡單實現,採用排序雙鏈表,T必須實現Comparable接口 8 */ 9 public class PriorityQueue<T extends Comparable<? extends T>> implements Queue<T> ,Serializable { 10 11 private static final long serialVersionUID = 8050142086009260625L; 12 13 private SortMyLinkedList<T> list;//排序循環雙鏈表 14 15 private boolean asc;//true表示升序,false表示降序 16 17 /** 18 * 用於控制最大容量,默認128,offer方法使用 19 */ 20 private int maxSize=128; 21 /** 22 * 初始化隊列 23 * @param asc 24 */ 25 public PriorityQueue(boolean asc){ 26 this.list=new SortMyLinkedList<>(); 27 this.asc=asc;//默認升序 28 } 29 30 public int getMaxSize() { 31 return maxSize; 32 } 33 34 public void setMaxSize(int maxSize) { 35 this.maxSize = maxSize; 36 } 37 38 @Override 39 public int size() { 40 return list.size(); 41 } 42 43 @Override 44 public boolean isEmpty() { 45 return list.isEmpty(); 46 } 47 48 /** 49 * data 入隊,添加成功返回true,不然返回false 50 * @param data 51 * @return 52 */ 53 @Override 54 public boolean add(T data) { 55 return list.add(data); 56 } 57 58 /** 59 * offer 方法可插入一個元素,這與add 方法不一樣, 60 * 該方法只能經過拋出未經檢查的異常使添加元素失敗。 61 * 而不是出現異常的狀況,例如在容量固定(有界)的隊列中 62 * NullPointerException:data==null時拋出 63 * IllegalArgumentException:隊滿,使用該方法可使Queue的容量固定 64 * @param data 65 * @return 66 */ 67 @Override 68 public boolean offer(T data) { 69 if (data==null) 70 throw new NullPointerException("The data can\'t be null"); 71 if (list.size()>=maxSize) 72 throw new IllegalArgumentException("The capacity of PriorityQueue has reached its maxSize:128"); 73 74 return add(data); 75 } 76 77 /** 78 * 返回隊頭元素,不執行刪除操做,若隊列爲空,返回null 79 * @return 80 */ 81 @Override 82 public T peek() { 83 if(isEmpty()){ 84 return null; 85 } 86 return this.asc ? this.list.get(0):this.list.get(size()-1); 87 } 88 89 /** 90 * 返回隊頭元素,不執行刪除操做,若隊列爲空,拋出異常:NoSuchElementException 91 * @return 92 */ 93 @Override 94 public T element() { 95 if(isEmpty()){ 96 throw new NoSuchElementException("The PriorityQueue is empty"); 97 } 98 return peek(); 99 } 100 101 /** 102 * 出隊,執行刪除操做,返回隊頭元素,若隊列爲空,返回null 103 * @return 104 */ 105 @Override 106 public T poll() { 107 if(isEmpty()){ 108 return null; 109 } 110 return this.asc ? this.list.remove(0): this.list.remove(list.size()-1); 111 } 112 113 /** 114 * 出隊,執行刪除操做,若隊列爲空,拋出異常:NoSuchElementException 115 * @return 116 */ 117 @Override 118 public T remove() { 119 if (isEmpty()){ 120 throw new NoSuchElementException("The PriorityQueue is empty"); 121 } 122 return poll(); 123 } 124 125 @Override 126 public void clearQueue() { 127 this.list.clear(); 128 } 129 130 //測試 131 public static void main(String[] args){ 132 PriorityQueue<Process> priorityQueue=new PriorityQueue<>(false); 133 134 System.out.println("初始化隊列"); 135 priorityQueue.add(new Process("進程1",10)); 136 priorityQueue.add(new Process("進程2",1)); 137 priorityQueue.add(new Process("進程3",8)); 138 priorityQueue.add(new Process("進程4",3)); 139 priorityQueue.add(new Process("進程5")); 140 System.out.println("隊列中的進程執行優先級:"); 141 while (!priorityQueue.isEmpty()){ 142 System.out.println("process:"+priorityQueue.poll().toString()); 143 } 144 145 } 146 147 }