吹牛必備之迴圈佇列與雙端迴圈佇列
阿新 • 來源:網路 • 發佈:2021-02-20
迴圈佇列
迴圈佇列概念
所謂的迴圈佇列,其實還是一個佇列,從外部呼叫的介面和實際的功能上看和普通的佇列完全沒有區別,而之所以它叫迴圈佇列而不是佇列,是因為它用於實現的底層邏輯和一般佇列不同,一般佇列底層採用雙向連結串列,而迴圈佇列底層採用索引不斷迴圈的陣列實現。
迴圈佇列操作圖示
建立迴圈佇列,勢必要先建立一個數組:
E elements=(E[]) new Object[8];//其中E表示泛型
假如向佇列中入隊11,22,33,44這四個元素,則其儲存結構如圖所示。

此時若將元素55入佇列,則直接在尾元素後入佇列:

倘若要出佇列,則稍稍複雜一些,將隊頭元素清空,並將front的指向後移:

假設在此基礎上出隊3次,併入隊66,77,88,則結果如下圖所示:

若在陣列沒有更大的索引位置可以使用時,還要繼續入隊99,則採用索引迴圈的方法,即放到左邊空餘的陣列空間。此時入隊,若元素從0開始放置(即front從0開始),入隊元素應該放置在索引為4即size的位置(因為已有4個元素,分別放置了0,1,2,3的位置),而此時由於front的偏移,入隊元素放置的位置也需要進行偏移,即size+front=8。而8明顯超出了陣列可以用的索引,此時將8對陣列大小取模,放置左側空餘的陣列空間(迴圈)。

再次入隊100,101,102:

此時佇列已經裝滿,若還要繼續入隊,則需要進行擴容(新建一個更大的陣列,將陣列從0開始遷移過去,並修改原陣列引用物件指向新陣列):
private void ensureCapacity(int newSize){
int oldCapacity=elements.length;
//如果容量可以承受則不擴容
if(newSize<=oldCapacity)return;
//否則擴容1.5
newSize=oldCapacity+(oldCapacity>>1);
E[] newElements= (E[]) new Object[newSize];
//資料遷移
for(int i=0;i<size;i++)
newElements[i]=elements[index(i)];
elements=newElements;
front=0;//指標歸零
System.out.println(oldCapacity+"擴容為"+newSize);
}
/**
* 簡化取模的索引對映
* @param index
* @return
*/
private int index(int index){
//避免取模運算(效率低下)
index+=front;
return index-(index>=elements.length?elements.length:0);
}
此時擴容後的結果將為:

然後再入隊:

以上就是迴圈佇列的操作步驟。
具體實現
public class CircleQueue<E>{
private int front;//指向頭節點
private int size;
private E[] elements;
//預設佇列大小
private static final int DEFAULT_CAPACITY=10;
public CircleQueue(){
this(DEFAULT_CAPACITY);
}
public CircleQueue(int capacity){
capacity=capacity>DEFAULT_CAPACITY?capacity:DEFAULT_CAPACITY;
elements= (E[]) new Object[capacity];
}
/**
* 獲取佇列大小
* @return
*/
public int size(){
return size;
}
/**
* 判斷陣列是否為空
* @return
*/
public boolean isEmpty(){
return size==0;
}
/**
* 清空佇列
*/
public void clear(){
//清空所有引用,等待jvm進行垃圾回收
for(int i=0;i<size;i++)
elements[index(i)]=null;
size=0;
//還原指標
front=0;
}
/**
* 入隊
*/
// 0 0 1 2 3
public void offer(E element){
ensureCapacity(size+1);
elements[index(size)]=element;//末尾入隊
size++;
}
private void ensureCapacity(int newSize){
int oldCapacity=elements.length;
//如果容量可以承受
if(newSize<=oldCapacity)return;
//否則擴容1.5
newSize=oldCapacity+(oldCapacity>>1);
E[] newElements= (E[]) new Object[newSize];
//資料遷移
for(int i=0;i<size;i++)
newElements[i]=elements[index(i)];
elements=newElements;
front=0;//指標歸零
System.out.println(oldCapacity+"擴容為"+newSize);
}
/**
* 簡化取模的索引對映
* @param index
* @return
*/
private int index(int index){
//避免取模運算(效率低下)
index+=front;
return index-(index>=elements.length?elements.length:0);
}
/**
* 出隊
* @return
*/
public E poll(){
E frontElement=elements[front];
elements[front]=null;
front=index(1);//更新指標到下一個元素
size--;
return frontElement;
}
/**
* 獲取隊頭元素
* @return
*/
public E peek(){
return elements[front];
}
@Override
public String toString() {
return "CircleQueue{" +
"front=" + front +
", size=" + size +
", elements=" + Arrays.toString(elements) +
'}';
}
}
迴圈雙端佇列
迴圈雙端佇列概念
迴圈雙端佇列其實就是一個雙端佇列,即可以在隊頭和隊尾都進行入隊或者出隊操作的佇列。它與迴圈佇列相比,也只是增加了在隊尾進行出隊操作的介面offerRear和在隊頭進行入隊操作的pollFront,其次由於在隊頭入隊需要獲取到原始索引為-1的對映索引(即頭節點之前的節點,頭節點原始索引為0),還需要對提供索引對映的index(int index)方法進行修改。
具體實現
public class CircleDeque<E> {
private int front;//指向頭節點
private int size;
private E[] elements;
//預設佇列大小
private static final int DEFAULT_CAPACITY=10;
public CircleDeque(){
this(DEFAULT_CAPACITY);
}
public CircleDeque(int capacity){
capacity=capacity>DEFAULT_CAPACITY?capacity:DEFAULT_CAPACITY;
elements= (E[]) new Object[capacity];
}
/**
* 獲取佇列大小
* @return
*/
public int size(){
return size;
}
/**
* 判斷陣列是否為空
* @return
*/
public boolean isEmpty(){
return size==0;
}
/**
* 清空佇列
*/
public void clear(){
//清空所有引用,等待jvm進行垃圾回收
for(int i=0;i<size;i++)
elements[index(i)]=null;
size=0;
//還原指標
front=0;
}
/**
* 從隊尾入隊
*/
// 0 0 1 2 3
public void offerRear(E element){
ensureCapacity(size+1);
elements[index(size)]=element;//末尾入隊
size++;
}
/**
* 從隊頭入隊
*/
public void offerFront(E element){
ensureCapacity(size+1);
int newFront=size==0?0:index(-1);//若放放入的是第一個元素,直接放在0處
elements[front=newFront]=element;//隊頭入隊並跟新front
size++;
}
private void ensureCapacity(int newSize){
int oldCapacity=elements.length;
//如果容量可以承受
if(newSize<=oldCapacity)return;
//否則擴容1.5
newSize=oldCapacity+(oldCapacity>>1);
E[] newElements= (E[]) new Object[newSize];
//資料遷移
for(int i=0;i<size;i++)
newElements[i]=elements[index(i)];
elements=newElements;
front=0;//指標歸零
System.out.println(oldCapacity+"擴容為"+newSize);
}
/**
* 簡化取模的索引對映
* @param index
* @return
*/
private int index(int index){
//防止index為-1
//避免取模操作
index+=front;
if(index<0)
return index+elements.length;
return index-(index>=elements.length?elements.length:0);
}
/**
* 從隊頭出隊
* @return
*/
public E pollFront(){
E frontElement=elements[front];
elements[front]=null;
front=index(1);//更新指標到下一個元素
size--;
return frontElement;
}
/**
* 從隊尾出隊
* @return
*/
public E pollRear(){
int rear=index(size-1);
E frontElement=elements[rear];
elements[rear]=null;//尾部出隊
size--;
return frontElement;
}
/**
* 獲取隊頭元素
* @return
*/
public E peekFront(){
return elements[front];
}
/**
* 獲取隊尾元素
* @return
*/
public E peekRear(){
return elements[index(size-1)];
}
@Override
public String toString() {
return "CircleDeque{" +
"front=" + front +
", size=" + size +
", elements=" + Arrays.toString(elements) +
'}';
}
}
