隊列(Queue)的定義:只允許在一端進行插入另一端進行刪除操作的線性表。允許插入的一端稱爲隊尾(rear) ,允許刪除的一端稱爲隊頭(front)。 具有“先進先出”特點。
隊列也是線性表,所以也存在順序結構和鏈式結構。
順序隊列:
對於隊列,入隊操作的解釋爲:
(是在隊尾追加一個元素,不需要移動任何元素,因此時間複雜度爲0(1)。)
- 判斷隊列是否已滿;
- 如果沒滿則先給隊尾元素賦值;
- 然後將隊尾指針後移一位(對隊尾指針賦值,Q->rear = Q->rear+1 )。
出隊操作解釋爲:
(隊列中的所有元素都得向前移動,以保證隊列的隊頭(也就是下標爲0的位置)不爲空,此時的時間複雜度爲0(n)。)
- 判斷隊列是否爲空;
- 若不爲空則將對首元素取出用來返回;
- 然後將對首指針後移一位(對隊首指針賦值,Q->front = Q->front+1 )。
- 隊列是否爲空判斷:隊首指針和隊尾指針進行判斷是否相等。
隊列的實際長度:隊尾指針-對首指針;也可以通過設定一個變量來進行存儲。
下面是我的順序隊列的Java實現:
package com.phn.queue;
/**
* @author 潘海南
* @Email [email protected]
* @TODO 順序隊列
* @date 2015年7月20日
*/
public class FOArrayQueue<E> {
//默認隊列容量
private static final int DEFUALT_CAPACITY = 100;
//隊列存儲數據元素的數組
private Object[] data = null;
//隊列的實際大小
private int size;
//隊列的對首索引
private int front;
//隊列的隊尾索引
private int rear;
//隊列的實際容量
private int capacity;
/**
* @TODO 無參構造函數,初始化隊列
*/
public FOArrayQueue() {
this(DEFUALT_CAPACITY);
}
/**
* @param initialCapacity 隊列初始化容量
*/
public FOArrayQueue(int initialCapacity) {
this.data = new Object[initialCapacity];
this.front = 0;
this.rear = 0;
this.size = 0;
this.capacity = initialCapacity;
}
/**
* @TODO 隊列入隊操作
* @param e 需要入隊的數據元素
* @return true
*/
public boolean enQueue(E e){
if(this.isFull()){
throw new RuntimeException("隊列已滿!最大容量="+this.capacity);
}
this.data[this.rear] = e;
// this.rear = (this.rear+1)%(this.capacity);
this.rear = this.rear+1;
this.size++;
return true;
}
/**
* @TODO 隊列的出隊操作
* @return e 位於對首的數據元素
*/
public E deQueue(){
if(this.isEmpty()){
throw new RuntimeException("隊列爲空!");
}
E e = (E)this.data[this.front];
// this.front = (this.front+1)%(this.capacity);
this.front = this.front+1;
this.size--;
return e;
}
/**
* @TODO 判斷隊列是否爲空
* @return true空 or false不爲空
*/
public boolean isEmpty(){
if(this.front==this.size){
return true;
}
return false;
}
/**
* @TODO 判斷隊列是否已滿
* @return true滿 or false未滿
*/
public boolean isFull(){
//這裏不能用size來進行判斷,用size會出現假溢出的情況
if(this.rear==this.capacity-1){
return true;
}
return false;
}
/**
* @TODO 獲取隊列的長度
* @return size
*/
public int size(){
return this.size;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer("[");
if(this.data[this.front]!=null){
sb.append(this.data[this.front]);
for (int i = this.front+1; i < this.rear; i++) {
sb.append(", "+this.data[i]);
}
}
sb.append("]");
return sb.toString();
}
}
下面是我的測試代碼:
public static void main(String[] args) {
FOArrayQueue<String> foaq = new FOArrayQueue<String>();
for (int i = 1; i <= 6; i++) {
foaq.enQueue("元素"+i);
}
System.out.println(foaq);
System.out.println(foaq.size());
System.out.println(foaq.deQueue());;
System.out.println(foaq);
System.out.println(foaq.size());
}
測試截圖:
由於隊列的入隊和出隊操作的結果導致隊列容易出現“假溢出”,於是乎出現了循環隊列。
循環隊列需要注意的地方如下:
- 執行入隊操作後,隊尾指針的變化:Q->rear = (Q->rear+1)% capacity。
- 執行出隊操作後,對首指針的變化:Q->front= (Q->front+1)% capacity。
- 判斷是否爲空的情況:Q->front=Q->rear。
- 判斷隊列是否爲滿的情況,需要放棄隊列一個位置來區分開來隊列是否爲空是否爲滿,這樣判斷條件爲:Q->front= (Q->rear + 1)% capacity。
- 隊列實際長度:(隊尾指針-對首指針+數組長度)% 數組長度;或者可以通過設定一個變量來進行存儲;或者都通過Q->front=Q->rear外加一個flag變量進行判斷。
好了,循環隊列也就介紹這些。
鏈隊列:
定義:隊列的鏈式存儲結構,其實就是線性表的單鏈表,只不過它只能尾進頭出而已,我們把它簡稱爲鏈隊列。
爲了方便操作,將隊頭指針指向鏈隊列的頭結點,而隊尾指針指向終端結點。
當隊列爲空時,front和rear都指向頭結點。
正如鏈隊就類似於單鏈表,這裏鏈隊的入隊、出隊、判斷爲空等操作也基本類似,這裏就不描述了,詳情請參考之前的單鏈表。
其實看了之前的再看這裏其實很容易的,不懂的話還可以參考這個網址:http://www.nowamagic.net/librarys/veda/detail/2357
循環隊列和鏈隊列的比較。
- 時間上:它們的基本操作都是常數時間O(1),這裏還有點細微的差別(循環隊列是事先申請好空間,使用期間不釋放,而對於鏈隊列,每次申請和釋放結點也會存在一些時間開銷,如果入隊出隊頻繁,則兩者還是有細微差異)。
- 空間上:循環隊列的長度固定,因此有了空間浪費的問題;而鏈隊列不存在這個問題,但是每個節點需要一個指針域,這是需要消耗一定的空間。相對來說,鏈隊列更加靈活。
總結:
在確定隊列長度最大值的情況,建議使用循環隊列;否則請使用鏈隊列。“循環隊列”最大優點就是節省空間和少分配空間,而鏈隊列多了一點點地址存儲開銷。