前一篇博客介绍了如何打造属于自己的数组,在实现数组的基础上,我们来完成对栈和队列的实现。
实现一个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();
}
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 进行取余操作
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倍的区别,主要是因为数组队列在出队操作上耗费了太多的时间。