棧與佇列的區別及自定義實現
一、棧(Stack)和佇列(Queue)的特點
棧(Stack)和佇列(Queue)是兩種基於陣列實現、操作受限的線性表,即棧和佇列都是陣列的子集。
線性表:線性表是一種線性結構,它是一個含有n≥0個結點的有限序列,同一個線性表中的元素資料型別相同並且滿足“一對一”的邏輯關係。
“一對一”的邏輯關係:指的是對於線性表中的結點,有且僅有一個開始結點沒有前驅但有一個後繼結點,有且僅有一個終端結點沒有後繼但有一個前驅結點,其它的結點都有且僅有一個前驅和一個後繼結點。
這種受限表現在:
- 棧的插入和刪除操作只允許在表的尾端進行(在棧中成為“棧頂”),滿足LOFI(Last Out First In )
- 佇列只允許在表尾插入資料元素,在表頭刪除資料元素,滿足FIFO(First In First Out)
棧與佇列的相同點:
1.都是線性結構。
2.插入操作都是限定在表尾進行。
3.都可以通過順序結構和鏈式結構實現。
4.插入與刪除的時間複雜度都是O(1),在空間複雜度上兩者也一樣。
5.多鏈棧和多鏈佇列的管理模式可以相同。
棧與佇列的不同點:
1.刪除資料元素的位置不同,棧的刪除操作在表尾進行,佇列的刪除操作在表頭進行。
2.應用場景不同;棧的常見應用場景包括括號問題的匹配,表示式的轉換和求值,函式呼叫和遞迴實現,深度優先搜尋遍歷等;佇列的常見應用場景包括計算機系統中各種資源的管理,訊息緩衝器的管理和廣度優先搜尋遍歷等。
3.順序棧能夠實現多棧空間共享,而順序佇列不能。
二、自定義棧的實現
1、定義Stack<E>介面
public interface Stack<E> {
// 入棧(壓棧),將元素放入棧中
void push(E e);
// 出棧(彈棧),從棧中取出元素
E pop();
// 檢視棧頂的元素
E peek();
// 獲取棧的大小
int getSize();
// 判斷棧是否為空
boolean isEmpty();
}
2、實現類ArrayStack<E>
public class ArrayStack<E> implements Stack<E> { MyArray<E> array; public ArrayStack() { // 預設容量為10 this.array = new MyArray<E>(); } public ArrayStack(int capacity) { this.array = new MyArray<E>(capacity); } // 壓棧 public void push(E e) { array.addLast(e); } // 取出棧頂元素 public E pop() { return array.removeLast(); } // 檢視棧頂的元素 public E peek() { return array.get(array.getSize() - 1); } // 獲取棧的大小 public int getSize() { return array.getSize(); } // 判斷棧是否為空 public boolean isEmpty() { return array.isEmpty(); } // 獲取棧的容量 public int getCapacity() { return array.getCapacity(); } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("Stack: ["); for (int i = 0; i < array.getSize(); i++) { sb.append(array.get(i)); if (i != array.getSize() - 1) { sb.append(","); } } sb.append("] top"); return sb.toString(); } }
應用場景:編輯器的do/undo操作、JVM虛擬機器棧+程式計數器,確定方法執行的順序
三、自定義佇列的實現
分別實現兩種佇列,即普通佇列、和迴圈佇列
主要區別在時間複雜度上,在實現佇列的動態擴減容後,普通佇列每次操作deQueue()方法,其時間複雜度都是O(n),而迴圈佇列每次操作deQueue()方法,其時間複雜度都是O(1)。
1、自定義介面
public interface Queue<E> {
// 獲取佇列的大小
int getSize();
// 判斷佇列是否為空
boolean isEmplt();
// 將元素放入佇列
void enqueue(E e);
// 取出佇列元素
E dequeue();
E getFront();
}
2、普通佇列的實現
public class ArrayQueue<E> implements Queue<E> {
private MyArray<E> array;
public ArrayQueue() {
this.array = new MyArray<E>();
}
public ArrayQueue(int capacity) {
this.array = new MyArray<E>(capacity);
}
public int getSize() {
return array.getSize();
}
public boolean isEmplt() {
return array.isEmpty();
}
public void enqueue(E e) {
// 此操作的時間複雜度,均攤
array.addLast(e);
}
public E dequeue() {
// 根據輸入的特性,此操作的時間複雜度為O(n)
return array.removeFirst();
}
public E getFront() {
return array.get(0);
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("Queue:");
sb.append("front [");
for (int i = 0; i < array.getSize(); i++) {
sb.append(array.get(i));
if (i != array.getSize() - 1) {
sb.append(", ");
}
}
sb.append("] tail");
return sb.toString();
}
}
3、迴圈佇列的實現
public class LoopQueue<E> implements Queue<E> {
private E[] data;
private int front;
private int tail;
private int size;
// 須預留佇列中的一個位置,因此根據需要,佇列長度加一
public LoopQueue(int capacity) {
data = (E[]) new Object[capacity + 1];
front = 0;
tail = 0;
size = 0;
}
// 預設佇列的容量為10
public LoopQueue() {
this(10);
}
// 獲取佇列大小
public int getSize() {
return size;
}
// 獲取佇列容量,因為根據需要資料長度加一,此時返回陣列容量需要減一
public int getCapacity() {
return data.length - 1;
}
// 判斷佇列是否為空,若front、tail指向同一個下標,即陣列為空
public boolean isEmplt() {
return front == tail;
}
// 向佇列中新增元素
public void enqueue(E e) {
if ((tail + 1) % data.length == front) {
resize(getCapacity() * 2);
}
data[tail] = e;
tail = (tail + 1) % data.length;
size++;
}
// 出隊操作
public E dequeue() {
if (this.isEmplt()) {
throw new IllegalArgumentException("The Queue is empty,can not dequeue any element");
}
// 獲取佇列第一個元素
E frontEle = data[front];
// 將被取出的元素置為null
data[front] = null;
// 更新front,記錄佇列的下一個元素的下標為front
front = (front + 1) % data.length;
size--;
// 實現動態減容
if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
resize(getCapacity() / 2);
}
return frontEle;
}
// 實現陣列動態擴減容
private void resize(int newCapacity) {
E[] newArray = (E[]) new Object[newCapacity + 1];
for (int i = 0; i < size; i++) {
newArray[i] = data[(i + front) % data.length];
}
data = newArray;
front = 0;
tail = size;
}
// 獲取佇列的元素
public E getFront() {
if (this.isEmplt()) {
throw new IllegalArgumentException("Queue is empty");
}
return data[front];
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity()));
sb.append("front [");
// 當front != tail時,表示佇列中存在元素
for (int i = front; i != tail; i = (i + 1) % data.length) {
sb.append(data[i]);
if ((i + 1) % data.length != tail) {
sb.append(", ");
}
}
sb.append("] tail");
return sb.toString();
}
}
4、時間複雜度測試
public class QueueTest {
public static double testQueue(Queue<Integer> queue, int count) {
// 開始時間,單位納米
long startTime = System.nanoTime();
Random random = new Random();
for (int i = 0; i < count; i++)
queue.enqueue(random.nextInt(Integer.MAX_VALUE));
for (int i = 0; i < count; i++) {
queue.dequeue();
}
// 結束時間,單位納米
long endTime = System.nanoTime();
// 程式執行消耗時間
return (endTime - startTime) / 1000000000.0;
}
public static void main(String[] args) {
// 定義操作的次數
int opNumber = 100000;
ArrayQueue<Integer> arrayQueue = new ArrayQueue<Integer>();
// 普通佇列
double simQueue = testQueue(arrayQueue, opNumber);
System.out.println("ArrayQueue, time: " + simQueue + " s");
// 虛幻佇列
LoopQueue<Integer> loopQueue = new LoopQueue<Integer>();
double lpQueue = testQueue(loopQueue, opNumber);
System.out.println("LoopQueue, time: " + lpQueue + " s");
System.out.println("執行時間相差倍數為times = " + (int) (simQueue / lpQueue));
}
}
測試結果:
針對執行環境的不同,可能測試結果會有些出處,但是執行時間的差異已經顯現無疑