用數組實現隊列(順序隊列&循環隊列)
順序隊列
↘️
隊列(先進先出)
幾個問題:
- 隊列方法:入隊、出隊
- 隊列的存儲:即隊首隊尾兩個指針,
- 擴容:如果隊列容量不夠了,應該擴容,如果隊尾沒有位置了,隊首有位置,應該把元素往前移
主要是上面三個問題,在代碼中都有體現,上面的擴容方法借鑒了ArrayList
的擴容方法。
package com.helius.structure.queue;
import java.util.Arrays;
/**
* 用數組實現一個隊列,即順序隊列
*/
public class ArrayQueue {
// 存儲數據的數組
private Object[] elements;
//隊列大小
private int size;
// 默認隊列容量
private int DEFAULT_CAPACITY = 10;
// 隊列頭指針
private int head;
// 隊列尾指針
private int tail;
private int MAX_ARRAY_SIZE = Integer.MAX_VALUE-8;
/**
* 默認構造函數 初始化大小為10的隊列
*/
public ArrayQueue(){
elements = new Object[DEFAULT_CAPACITY];
initPointer(0,0);
}
/**
* 通過傳入的容量大小創建隊列
* @param capacity
*/
public ArrayQueue(int capacity){
elements = new Object[capacity];
initPointer(0,0);
}
/**
* 初始化隊列頭尾指針
* @param head
* @param tail
*/
private void initPointer(int head,int tail){
this.head = head;
this.tail = tail;
}
/**
* 元素入隊列
* @param element
* @return
*/
public boolean enqueue(Object element){
ensureCapacityHelper();
elements[tail++] = element;//在尾指針處存入元素且尾指針后移
size++;//隊列元素個數加1
return true;
}
private void ensureCapacityHelper() {
if(tail==elements.length){//尾指針已越過數組尾端
//判斷隊列是否已滿 即判斷數組中是否還有可用存儲空間
//if(size<elements.length){
if(head==0){
//擴容
grow(elements.length);
}else{
//進行數據搬移操作 將數組中的數據依次向前挪動直至頂部
for(int i= head;i<tail;i++){
elements[i-head]=elements[i];
}
//數據搬移完后重新初始化頭尾指針
initPointer(0,tail-head);
}
}
}
/**
* 擴容
* @param oldCapacity 原始容量
*/
private void grow(int oldCapacity) {
int newCapacity = oldCapacity+(oldCapacity>>1);
if(newCapacity-oldCapacity<0){
newCapacity = DEFAULT_CAPACITY;
}
if(newCapacity-MAX_ARRAY_SIZE>0){
newCapacity = hugeCapacity(newCapacity);
}
elements = Arrays.copyOf(elements,newCapacity);
}
private int hugeCapacity(int newCapacity) {
return (newCapacity>MAX_ARRAY_SIZE)? Integer.MAX_VALUE:newCapacity;
}
/**
* 出隊列
* @return
*/
public Object dequeue(){
if(head==tail){
return null;//隊列中沒有數據
}
Object obj=elements[head++];//取出隊列頭的元素且頭指針后移
size--;//隊列中元素個數減1
return obj;
}
/**
* 獲取隊列元素個數
* @return
*/
public int getSize() {
return size;
}
}
測試用例
public class TestArrayQueue {
public static void main(String[] args) {
ArrayQueue queue = new ArrayQueue(4);
//入隊列
queue.enqueue("helius1");
queue.enqueue("helius2");
queue.enqueue("helius3");
queue.enqueue("helius4");
//此時入隊列應該走擴容的邏輯
queue.enqueue("helius5");
queue.enqueue("helius6");
//出隊列
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
//此時入隊列應該走數據搬移邏輯
queue.enqueue("helius7");
//出隊列
System.out.println(queue.dequeue());
//入隊列
queue.enqueue("helius8");
//出隊列
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
//入隊列
queue.enqueue("helius9");
queue.enqueue("helius10");
queue.enqueue("helius11");
queue.enqueue("helius12");
//出隊列
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
}
}
結果:
helius1
helius2
helius3
helius4
helius5
helius6
helius7
helius8
null
helius9
helius10
循環隊列
用java實現循環隊列的方法:
-
增加一個屬性size用來記錄目前的元素個數。目的是當head=rear的時候,通過size=0還是size=數組長度,來區分隊列為空,或者隊列已滿。
-
數組中只存儲數組大小-1個元素,保證rear轉一圈之后不會和head相等,也就是隊列滿的時候,rear+1=head,中間剛好空一個元素。
當rear=head的時候,一定是隊列空了。
隊列(Queue)兩端允許操作的類型不一樣:
可以進行刪除的一端稱為隊頭,這種操作也叫出隊dequeue;
可以進行插入的一端稱為隊尾,這種操作也叫入隊enqueue。
隊列的示意圖
實現隊列時,要注意的是假溢出現象,如上圖的最后一幅圖。
如圖所示的假溢出現象,順序隊列可以如此,循環隊列我們可以讓這個尾指針指向front前面的元素,這也正符合我們想要的循環隊列的定義。
解決辦法:使用鏈式存儲,這顯然可以。在順序存儲時,我們常見的解決辦法是把它首尾相接,構成循環隊列,這可以充分利用隊列的存儲空間。
循環隊列示意圖:
在上圖中,front指向隊列中第一個元素,rear指向隊列隊尾的下一個位置。
但依然存在一個問題:當front和rear指向同一個位置時,這代表的是隊空還是隊滿呢?大家可以想象下這種情景。
解決這種問題的常見做法是這樣的:
使用一標記,用以區分這種易混淆的情形。
犧牲一個元素空間。當front和rear相等時,為空;當rear的下一個位置是front時,為滿。
如下圖:
下面我們給出循環隊列,並采用第二種方式,即犧牲一個元素空間來區分隊空和隊滿的代碼.
幾個重點:
1、front指向隊頭,rear指向隊尾的下一個位置。
2、隊為空的判斷:frontrear;隊為滿的判斷:(rear+1)%MAXSIZEfront。
上面說的rear即為代碼中的的tail
/**
* 使用數組實現循環隊列
* @author Helius
*/
public class CirculiQueue {
//存儲隊列數據的數組
private Object[] elements;
//默認數組容量
private int DEFAULT_CAPACITY=10;
//隊列中元素個數
private int size;
// 隊列頭指針
private int head;
//隊列尾指針
private int tail;
/**
* 默認構造函數
*/
public CirculiQueue(){
elements = new Object[DEFAULT_CAPACITY];
}
/**
* 通過傳入的容量參數構造隊列
* @param capacity
*/
public CirculiQueue(int capacity){
elements = new Object[capacity];
}
/**
* 元素入隊列
* @param element
* @return
*/
public boolean enqueue(Object element){
//判斷隊列是否已滿
if(head == (tail+1)%elements.length){
//隊列已滿
return false;
}
//將元素存入tail位置上
elements[tail]=element;
//尾指針后移
/*tail++;
if(tail==elements.length){
tail = 0;
}*/
tail = (tail+1)%elements.length;
size++;
return true;
}
/**
* 元素出隊列
* @return
*/
public Object dequeue(){
//判斷隊列是否為空
if(head==tail){
return null;
}
//獲取head位置上的元素
Object element = elements[head];
//頭指針后移
/*head++;
if(head==elements.length){
head = 0;
}*/
head = (head+1)%elements.length;
size--;
return element;
}
/**
* 獲取隊列大小
* @return
*/
public int getSize() {
return size;
}
}
這里也添加了size屬性,可以稍做修改,來實現第一種方式。