隊列,一種作爲構思算法的輔助數據結構,和棧相反,遵循FIFO即先進先出原則。
爲了節省空間,提高底層數據結構的使用率(隊列可以通過數組實現,也可以通過鏈表實現),常見的做法和其他數據結構擴容不同,並不是尾指針移至末尾就進行擴容,而是將尾指針移至0,從頭開始(當然這要求底層數據結構如數組,起始的位置數據已經pop出去了,否則將進行擴容)。如下所示:
如圖我們再次pop一個3的時候,並不像其他數據結構一樣,立即擴容,而是將隊尾指針移至0,將3這個元素push到0索引的位置:
這樣做的根本目的就是爲了節省空間,當然這會在算法實現上帶來一系列難度。尤其在擴容的時候,假設我們在上圖所示隊列中,再push兩個元素3,那麼隊尾指針到達2,對頭指針不變,這個沒有什麼問題,但是在我們還想push一個元素的時候,這個時候如果像其他數據結構一樣,進行簡單的擴容是不能滿足的。如下:
如果只是簡單的擴容,我們再想push元素的時候,隊尾指針上移,但是上移之後和隊頭指針衝突,我們這樣的擴容是不行的。
解決思路:
1. 和其他數據結構一樣,尾指針到達數組(以下都以數組實現隊列爲例)末尾的時候,不掉頭,直接進行擴容。雖然可能大部分程序猿實現隊列都是掉頭,但是我們要知道這樣做的根本目的是節省空間,而不是什麼規範和協議必須遵守。由此引來的問題就是空間浪費問題。
2. 那麼爲什麼大多數程序猿都選擇尾指針掉頭呢,因爲本文所提及的擴容問題,解決方案並不難,我們可以在copy的時候,將隊頭指針到數組末尾的數據和數組開始到隊尾指針的位置分開copy,雖然複製了兩次,但是數組複製我們都調用System.arraycopy,利用他們內存地址連續,快速操作,不用循環(可自行百度數組內部尋址)。
如下所示,我們將隊頭至數組末尾的數據1,2,3,4,在擴容的時候,移至新數組的末尾,同時隊頭位置發生改變。
3. 爲了節省空間,我們可以在每次push的時候,調用一個是否需要縮減容量的處理方法,比如,如果當前隊列數組長度爲20,但是實際數據量只有10,我們可以進行一次容量縮減,如果實在有此需要,建議將數組容量的1/2作爲標準。
代碼實現:
package com.dzh.learn.dataStructuresAndAlgorithm.dataStructures;
import java.util.Arrays;
/**
* 隊列
* FIFO first in first out 先進先出
* 單向隊列
* @author caiwm
*/
public class MyQueue {
private int[] arr;
private int defCapacity = 10;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/*** 實際長度 */
private int size;
/*** 隊頭 先出 */
private int queueHead;
/*** 隊尾 後入 */
private int queueEnd;
public MyQueue(int initCapacity){
if (initCapacity <= 0)
throw new RuntimeException("Capacity must greater than zero.");
arr = new int[initCapacity];
}
public MyQueue(){
arr = new int[defCapacity];
}
public void push(int element){
ensureCapacityInternal();
// TODO 縮減整理 例如空閒空間大於1/2
if (size == 0){
queueHead = queueEnd = 0;
arr[0] = element;
} else{
// 最上 返回0賦值
if (queueEnd == (arr.length - 1)){
arr[0] = element;
queueEnd = 0;
} else {
arr[++queueEnd] = element;
}
}
size++;
}
private void ensureCapacityInternal(){
if (arr.length == size){
grow();
}
}
private void grow(){
// int 最大值校驗
int capacityAdd = ensureExplicitCapacityAdd();
if (queueEnd < queueHead){
// 尾指針掉頭 copy方案
int[] arrNew = new int[arr.length + capacityAdd];
int numMoved = arr.length - queueHead;
System.arraycopy(arr, queueHead, arrNew, (queueHead + capacityAdd), numMoved);
System.arraycopy(arr, 0, arrNew, 0, queueHead);
queueHead += capacityAdd;
arr = arrNew;
} else {
arr = Arrays.copyOf(arr, arr.length + capacityAdd);
}
}
private int ensureExplicitCapacityAdd(){
if (arr.length + 1 > MAX_ARRAY_SIZE)
return ((arr.length + 1 > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE) - arr.length;
else
return arr.length >> 1;
}
public int peek(){
if (isEmpty())
throwEmptyException();
return arr[queueHead];
}
public int pop(){
if (isEmpty())
throwEmptyException();
int i = arr[queueHead];
arr[queueHead] = 0; // 爲了大家看得更直觀,其實可以不需要,但是Object類型queue或者泛型queue,此處最好置爲null
if (queueHead == (arr.length - 1)){
queueHead = 0;
} else {
queueHead++;
}
size--;
return i;
}
public boolean isEmpty(){
return size == 0;
}
public void throwEmptyException(){
throw new RuntimeException("Queue is empty.");
}
}