數據結構學習之——隊列與循環隊列java
隊列(Queue)同棧(stack)同樣也是一種運算收限的線性數據結構,參考:數據結構之——棧。棧即:LIFO(Last In First Out),隊列則是FIFO(First In First Out),也就是說隊列這種數據結構僅容許在一端(隊尾)添加元素,在另外一端(隊首)刪除元素,因此說隊列是一種先進先出的數據結構。能夠將隊列想象成銀行櫃檯的排隊機制同樣,在前面排隊的人能夠先辦理業務,在最後排隊的人等到前面全部的人辦理完畢後,才能夠進行業務的處理,如圖:
git
隊列同ArrayStack的實現同樣,都須要基於動態數組做爲底層實現。
動態數組實現代碼,動態數組實現原理
在設計模式上,同ArrayStack同樣,設計的是Queue這樣一個接口,並建立ArrayQueue這樣一個類implements這個接口,Queue接口的方法與Stack棧的方法大致相同,只不過咱們將入棧push設計成enqueue(入隊),出棧pop設計爲dequeue(出隊)。接口代碼以下:github
public interface Queue<E>{
void enqueue(E e);
E dequeue();
E getFront();
int getSize();
boolean is Empty();
}
複製代碼
ArrayQueue代碼以下:算法
public class ArrayQueue<E> implements Queue<E>{
private Array<E> array;
public ArrayQueue(int capacity){
array = new Array<>(capacity);
}
public ArrayQueue(){
array = new Array<>();
}
@Override
public int getSize(){
return array.getSize();
}
@Override
public boolean isEmpty(){
return array.isEmpty();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public void enqueue(E e){
array.addLast(e);
}
@Override
public E dequeue(){
return array.removeFirst();
}
@Override
public E getFront(){
if(array.isEmpty)
throw new IllegalArgumentException("ArrayQueue is Empty");
return array.get(0)
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("Queue:\n");
sb.append("front:[");
for(int i=0;i<getSize();i++){
sb.append(array.get(i));
if(i!=getSize()-1){
sb.append(",");
}else{
sb.append("]tail");
}
}
return sb.toString();
}
}
複製代碼
時間複雜度分析以下:設計模式
E getFront();
int getSize();
boolean is Empty();
複製代碼
以上方法,時間複雜度爲:O(1)。數組
void enqueue(E e);
複製代碼
由於入隊操做至關於在動態數組的尾部添加元素,雖然有resize()這樣一個O(n)級別的算法,可是以均攤時間複雜度分析,enqueue操做仍然是一個O(1)級別的算法。bash
E dequeue();
複製代碼
dequeue()操做至關於動態數組的removeFirst()操做,在數組的頭部刪除一個元素,array[0] 後面的全部元素都須要向前挪動一個位置,因此dequeue出隊是一個O(n)級別的算法。
綜上分析,ArrayQueue仍是有些不完美的地方,ArrayStack全部的操做均爲O(1)級別的算法,可是基於動態數組實現的隊列ArrayQueue 在出隊操做dequeue上性能仍是略顯不足,LoopQueue(循環隊列)就優化了ArrayQueue出隊這樣一個問題。數據結構
咱們已經知道了,ArrayQueue美中不足的地方就在於dequeue這樣一個操做是O(n)級別的算法。出現這個問題的緣由其實是由於每次進行出隊操做時,動態數組都須要將array[0]後面全部的元素向前挪動一個單位,但實際上想想這個過程並不「划算」,由於,隊列元素的數量達到萬以上的級別時,僅僅刪除一個元素,咱們就須要將全部的元素進行一次大換血。和銀行櫃檯業務辦理的排隊不一樣,銀行櫃檯的一號辦理人辦理完畢,後面全部的人只須要上前一小步就能夠了,可是對於計算機來說,每個數據的調整都須要計算機親歷躬行。有什麼辦法能夠避免這種大規模的動輒呢?咱們可使用兩個變量去維護隊列的隊首和隊尾。
app
public class MyQueue<E> implements Queue<E> {
private int front;
private int tail;
private E[]data;
public MyQueue(int capacity){
data = (E[])new Object[capacity+1];
}
public MyQueue(){
this(10);
}
@Override
public int getSize(){
return tail-front;
}
@Override
public boolean isEmpty(){
return front==tail;
}
@Override
public E getFront(){
return data[front];
}
private void resize(int newCapacity){
E[]newData = (E[])new Object[newCapacity+1];
for(int i=0;i<(tail-front);i++){
newData[i] = data[front+i];
}
data = newData;
tail = tail - front;
front = 0;
}
@Override
public void enqueue(E e){
if(tail == data.length-1)
resize((tail-front)*2);
data[tail] = e;
tail++;
}
@Override
public E dequeue(){
E ret = data[front];
// Loitering Object
data[front] = null;
front++;
if(((tail-front)==(data.length-1)/4) && (data.length-1)/2!=0)
resize((data.length-1)/2);
return ret;
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(String.format("MyQueue size=%d,capacity=%d\n",tail-front,data.length-1));
stringBuilder.append("front:[");
for(int i=front;i<tail;i++){
stringBuilder.append(data[i]);
if((i+1)!=tail){
stringBuilder.append(",");
}
}
stringBuilder.append("]tail");
return stringBuilder.toString();
}
}
複製代碼
MyQueue對ArrayQueue進行了優化操做,本來dequeue須要O(n)的時間複雜度,進行優化後,dequeue由O(n)的時間複雜度變爲了均攤O(1)的時間複雜度。這裏面須要注意的是:MyQueue的enqueue入隊操做規定了tail == data.length-1時進行「擴容」操做,這裏面擴容二字我使用了雙引號,由於有可能這個「擴容」其實是縮容。
dom
if(tail == data.length-1)
resize((tail-front)*2);
複製代碼
在上圖的示例中,若是reisze,咱們實際上就至關於進行了縮容的操做。咱們這樣的設計看起來解決了問題,但仍然不靈活,咱們但願在入隊時的resize只涉及到擴容,出隊時的resize只涉及縮容,咱們是否能對這樣的需求進行優化呢?
循環隊列的思想和ArrayQueue優化後的MyQueue大致相同,只不過循環隊列裏面加入了更加巧妙的循環機制。
public class LoopQueue<E> implements Queue<E> {
private E[]data;
private int front;
private int tail;
public LoopQueue(int capacity){
data = (E[])new Object[capacity+1];
}
public LoopQueue(){
this(10);
}
@Override
public int getSize(){
if(tail<front)
return data.length-(front-tail);
else{
return tail-front;
}
}
public int getCapacity(){
return data.length-1;
}
@Override
public boolean isEmpty(){
return getSize()==0;
}
private void resize(int newCapacity){
E[]newData = (E[])new Object[newCapacity+1];
for(int i=0;i<getSize();i++){
newData[i] = data[(i+front)%data.length];
}
data = newData;
tail = getSize();
front = 0;
}
@Override
public void enqueue(E e){
if(front==(tail+1)%data.length)
resize(2*getSize());
data[tail] = e;
tail = (tail+1)%data.length;
}
@Override
public E dequeue(){
E ret = data[front];
// Loitering Object
data[front] = null;
front = (front+1)%data.length;
if(getSize() == getCapacity()/4 && getCapacity()/2!=0){
resize(getCapacity()/2);
}
return ret;
}
@Override
public E getFront(){
if(getSize()==0)
throw new IllegalArgumentException("LoopQueue is empty");
return data[front];
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(String.format("LoopQueue size:%d,capacity:%d\n",getSize(),getCapacity()));
stringBuilder.append("front:[");
for(int i=front;i!=tail;i=(i+1)%data.length){
stringBuilder.append(data[i]);
if((i+1)%data.length!=tail)
stringBuilder.append(",");
}
stringBuilder.append("]tail");
return stringBuilder.toString();
}
}
複製代碼
如今咱們對ArrayQueue,LoopQueue進行性能上的測試,
import java.util.Random;
public class Main {
private static double testQueue(Queue<Integer>q,int testCount){
long startTime = System.nanoTime();
Random random = new Random();
for(int i=0;i<testCount;i++)
q.enqueue(random.nextInt(Integer.MAX_VALUE));
for(int i=0;i<testCount;i++)
q.dequeue();
long endTime = System.nanoTime();
return (endTime-startTime)/1000000000.0;
}
public static void main(String[]args){
int testCount = 100000;
ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
LoopQueue<Integer> loopQueue = new LoopQueue<>();
double loopQueueTime = testQueue(loopQueue,testCount);
double arrayQueueTime = testQueue(arrayQueue,testCount);
System.out.println("LoopQueue:"+loopQueueTime);
System.out.println("ArrayQueue"+arrayQueueTime);
}
}
複製代碼
在jdk1.8的環境下測試結果爲: