打造屬於自己的棧和隊列
前一篇博客介紹瞭如何打造屬於自己的數組,在實現數組的基礎上,我們來完成對棧和隊列的實現。
棧
- 在棧頂壓棧
- 在棧頂出棧
棧的應用
- 無處不在的Undo操作(撤銷操作)
- 程序調用的系統棧,函數A中調用另外的函數B,先將函數B入棧,執行完畢函數B出棧,在執行函數A。
實現一個棧的接口
實現一個Stack的接口:
package design.stack;
/**
* @author cyj
*/
public interface Stack<E> {
/**
* 獲取棧的長度
*
* @return
*/
int getSize();
/**
* 判斷棧是否爲空
*
* @return
*/
boolean isEmpty();
/**
* 將一個數壓入棧中
*
* @param e
*/
void push(E e);
/**
* 移除棧頂元素,返回被刪除的元素
*
* @return
*/
E pop();
/**
* 獲取棧頂元素
*
* @return
*/
E peek();
}
實現一個棧(基於數組)
package design.stack;
import design.array.ArrayObject;
/**
* @program: design
* @description: 數組棧具體實現類
* @author: cyj
* @create: 2019-03-20 10:42
**/
public class ArrayStack<E> implements Stack<E> {
ArrayObject<E> array;
public ArrayStack(int capacity) {
array = new ArrayObject<>(capacity);
}
public ArrayStack() {
array = new ArrayObject<>();
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
public int getCapacity() {
return array.getCapacity();
}
@Override
public void push(E e) {
array.addLast(e);
}
@Override
public E pop() {
return array.removeLast();
}
@Override
public E peek() {
return array.getLast();
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Stack: ");
res.append('[');
for (int i = 0; i < array.getSize(); i++) {
res.append(array.get(i));
if (i != array.getSize() - 1) {
res.append(", ");
}
}
res.append("] top");
return res.toString();
}
}
測試棧類
package design.stack;
/**
* @program: design
* @description: 棧測試類
* @author: cyj
* @create: 2019-03-20 11:03
**/
public class StackTest {
public static void main(String[] args) {
ArrayStack<Integer> stack = new ArrayStack<>();
//循環賦值,將整數壓入棧中
for (int i = 0; i < 5; i++) {
stack.push(i);
System.out.println(stack);
}
//在去除棧頂元素
stack.pop();
System.out.println(stack);
}
}
運行結果:(top)爲棧頂元素
Stack: [0] top
Stack: [0, 1] top
Stack: [0, 1, 2] top
Stack: [0, 1, 2, 3] top
Stack: [0, 1, 2, 3, 4] top
Stack: [0, 1, 2, 3] top
隊列
- 在隊尾入隊
- 在隊首出隊
實現一個數組隊列
在數組的基礎上實現一個隊列,需要實現以下方法
- 先定義一個隊列接口:
package design.queue;
/**
* @author cyj
*/
public interface Queue<E> {
/**
* 獲取隊列的大小
*
* @return
*/
int getSize();
/**
* 判斷隊列是否爲空
*
* @return
*/
boolean isEmpty();
/**
* 將一個數添加入隊尾中
*
* @param e
*/
void enqueue(E e);
/**
* 去除隊首元素,返回被刪除的元素
*
* @return
*/
E dequeue();
/**
* 獲取隊列元素
*
* @return
*/
E getFront();
}
- 實現Queue接口
package design.queue;
import design.array.ArrayObject;
/**
* @program: design
* @description: Array的隊列實現類
* @author: cyj
* @create: 2019-03-20 14:16
**/
public class ArrayQueue<E> implements Queue<E> {
private ArrayObject<E> array;
public ArrayQueue(int capacity) {
array = new ArrayObject<>(capacity);
}
public ArrayQueue() {
array = new ArrayObject<>();
}
@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() {
//時間複雜度爲O(n),刪除隊首元素之後,需要將隊列元素往前移一位,數據量一上去就會非常耗時間,因此需要改進
//下文會對循環隊列進行介紹
return array.removeFirst();
}
@Override
public E getFront() {
return array.getFirst();
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Queue: ");
res.append("front [");
for (int i = 0; i < array.getSize(); i++) {
res.append(array.get(i));
if (i != array.getSize() - 1) {
res.append(", ");
}
}
res.append("] tail");
return res.toString();
}
public static void main(String[] args) {
ArrayQueue<Integer> queue = new ArrayQueue<>();
for (int i = 0; i < 10; i++) {
queue.enqueue(i);
System.out.println(queue);
if (i % 3 == 2) {
queue.dequeue();
System.out.println(queue);
}
}
}
}
//每在隊尾入隊3個元素之後再出隊一個隊首元素
運行結果:
Queue: front [0] tail
Queue: front [0, 1] tail
Queue: front [0, 1, 2] tail
Queue: front [1, 2] tail
Queue: front [1, 2, 3] tail
Queue: front [1, 2, 3, 4] tail
Queue: front [1, 2, 3, 4, 5] tail
Queue: front [2, 3, 4, 5] tail
Queue: front [2, 3, 4, 5, 6] tail
Queue: front [2, 3, 4, 5, 6, 7] tail
Queue: front [2, 3, 4, 5, 6, 7, 8] tail
Queue: front [3, 4, 5, 6, 7, 8] tail
Queue: front [3, 4, 5, 6, 7, 8, 9] tail
實現一個循環隊列
因爲在數組隊列中,每出隊一個隊首元素,都需要將之後的元素往前位移,這相當的耗時間,時間複雜度爲O(n)。那有沒有方法降低時間複雜度呢?
這個時候就輪到循環隊列閃亮登場,循環隊列通過雙指針的方式維護整個隊列,出隊的情況下不用整個位移,而是將指向隊首的指針往後移一位就可以。那麼在入隊的時候,則將指向隊尾的指針向後移。那麼問題來了,指向隊尾的指針如果已經到了隊列的最長長度,但是隊首之前由於執行過出隊操作而騰出來空間,這時就可以把指向隊尾的指針指向隊列的前端,形成一個閉環,因此也有了判斷隊列是否爲空爲滿的判斷依據。
-
判斷循環隊列爲空:front==tail
-
判斷循環隊列爲滿:(tail+1)%length=front 進行取餘操作
-
根據判斷爲滿的條件可以看出,循環隊列實際上浪費了一個空間,因此在實例化中需要將循環隊列的空間+1
package design.queue;
/**
* @program: design
* @description: 循環隊列實現類
* @author: cyj
* @create: 2019-03-20 14:46
**/
public class LoopQueue<E> implements Queue<E> {
private E[] data;
private int front, tail;
private int size;
public LoopQueue(int capacity) {
//循環隊列判斷條件,有意的會浪費一個單位,這樣才能容納下用戶期望的隊列長度
data = (E[]) new Object[capacity + 1];
front = 0;
tail = 0;
size = 0;
}
public LoopQueue() {
this(10);
}
public int getCapacity() {
return data.length - 1;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return front == tail;
}
@Override
public void enqueue(E e) {
if ((tail + 1) % data.length == front) {
resize(getCapacity() * 2);
}
data[tail] = e;
tail = (tail + 1) % data.length;
size++;
}
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity + 1];
for (int i = 0; i < size; i++) {
newData[i] = data[(i + front) % data.length];
}
data = newData;
front = 0;
tail = size;
}
/**
* O(1) 均攤複雜度
*
* @return
*/
@Override
public E dequeue() {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}
E ret = data[front];
data[front] = null;
front = (front + 1) % data.length;
size--;
if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
resize(getCapacity() / 2);
}
return ret;
}
@Override
public E getFront() {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}
return data[front];
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Queue: size= %d , capacity= %d\n", size, getCapacity()));
res.append("front [");
//重點!!判斷條件多想想
for (int i = front; i != tail; i = (i + 1) % data.length) {
res.append(data[i]);
if ((i + 1) % data.length != tail) {
res.append(", ");
}
}
res.append("] tail");
return res.toString();
}
public static void main(String[] args) {
LoopQueue<Integer> queue = new LoopQueue<>();
for (int i = 0; i < 10; i++) {
queue.enqueue(i);
System.out.println(queue);
if (i % 3 == 2) {
queue.dequeue();
System.out.println(queue);
}
}
}
}
//每在隊尾入隊3個元素之後再出隊一個隊首元素
//注意觀察循環隊列的capacity
輸出結果:
Queue: size= 1 , capacity= 10
front [0] tail
Queue: size= 2 , capacity= 10
front [0, 1] tail
Queue: size= 3 , capacity= 10
front [0, 1, 2] tail
Queue: size= 2 , capacity= 5
front [1, 2] tail
Queue: size= 3 , capacity= 5
front [1, 2, 3] tail
Queue: size= 4 , capacity= 5
front [1, 2, 3, 4] tail
Queue: size= 5 , capacity= 5
front [1, 2, 3, 4, 5] tail
Queue: size= 4 , capacity= 5
front [2, 3, 4, 5] tail
Queue: size= 5 , capacity= 5 //實際開閉了6個空間,所以在下一步才進行擴容
front [2, 3, 4, 5, 6] tail
Queue: size= 6 , capacity= 10
front [2, 3, 4, 5, 6, 7] tail
Queue: size= 7 , capacity= 10
front [2, 3, 4, 5, 6, 7, 8] tail
Queue: size= 6 , capacity= 10
front [3, 4, 5, 6, 7, 8] tail
Queue: size= 7 , capacity= 10
front [3, 4, 5, 6, 7, 8, 9] tail
數組隊列與循環隊列的比較
分別執行100000次的入隊操作和100000次的出隊操作,查看運行時間
package design.queue;
import java.util.Random;
/**
* @program: design
* @description: 數組隊列和循環隊列的比較
* @author: cyj
* @create: 2019-03-20 16:20
**/
public class QueueTest {
/**
* 測試使用q運行opCount個enqueue和dequeue操作所需要的時間,單位:秒
*
* @param q
* @param opCount
* @return
*/
private static double testQueue(Queue<Integer> q, int opCount) {
long startTime = System.nanoTime();
Random random = new Random();
for (int i = 0; i < opCount; i++) {
q.enqueue(random.nextInt(Integer.MAX_VALUE));
}
for (int i = 0; i < opCount; i++) {
q.dequeue();
}
long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
}
public static void main(String[] args) {
int opCount = 100000;
ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
double time1 = testQueue(arrayQueue, opCount);
System.out.println("ArrayQueue , time: " + time1 + " s");
LoopQueue<Integer> loopQueue = new LoopQueue<>();
double time2 = testQueue(loopQueue, opCount);
System.out.println("LoopQueue , time: " + time2 + " s");
}
}
運行結果:
ArrayQueue , time: 3.311457298 s
LoopQueue , time: 0.012101963 s
可以看出有明顯的區別,甚至可以說100倍的區別,主要是因爲數組隊列在出隊操作上耗費了太多的時間。