栈(Stack)和队列(Queue)是两种基于数组实现、操作受限的线性表,即栈和队列都是数组的子集。
线性表:线性表是一种线性结构,它是一个含有n≥0个结点的有限序列,同一个线性表中的元素数据类型相同并且满足“一对一”的逻辑关系。
“一对一”的逻辑关系:指的是对于线性表中的结点,有且仅有一个开始结点没有前驱但有一个后继结点,有且仅有一个终端结点没有后继但有一个前驱结点,其它的结点都有且仅有一个前驱和一个后继结点。
这种受限表现在:
栈与队列的相同点:
1.都是线性结构。
2.插入操作都是限定在表尾进行。
3.都可以通过顺序结构和链式结构实现。
4.插入与删除的时间复杂度都是O(1),在空间复杂度上两者也一样。
5.多链栈和多链队列的管理模式可以相同。
栈与队列的不同点:
1.删除数据元素的位置不同,栈的删除操作在表尾进行,队列的删除操作在表头进行。
2.应用场景不同;栈的常见应用场景包括括号问题的匹配,表达式的转换和求值,函数调用和递归实现,深度优先搜索遍历等;队列的常见应用场景包括计算机系统中各种资源的管理,消息缓冲器的管理和广度优先搜索遍历等。
3.顺序栈能够实现多栈空间共享,而顺序队列不能。
1、定义Stack
public interface Stack {
// 入栈(压栈),将元素放入栈中
void push(E e);
// 出栈(弹栈),从栈中取出元素
E pop();
// 查看栈顶的元素
E peek();
// 获取栈的大小
int getSize();
// 判断栈是否为空
boolean isEmpty();
}
2、实现类ArrayStack
public class ArrayStack implements Stack {
MyArray array;
public ArrayStack() {
// 默认容量为10
this.array = new MyArray();
}
public ArrayStack(int capacity) {
this.array = new MyArray(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 {
// 获取队列的大小
int getSize();
// 判断队列是否为空
boolean isEmplt();
// 将元素放入队列
void enqueue(E e);
// 取出队列元素
E dequeue();
E getFront();
}
2、普通队列的实现
public class ArrayQueue implements Queue {
private MyArray array;
public ArrayQueue() {
this.array = new MyArray();
}
public ArrayQueue(int capacity) {
this.array = new MyArray(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 implements Queue {
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 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 arrayQueue = new ArrayQueue();
// 普通队列
double simQueue = testQueue(arrayQueue, opNumber);
System.out.println("ArrayQueue, time: " + simQueue + " s");
// 虚幻队列
LoopQueue loopQueue = new LoopQueue();
double lpQueue = testQueue(loopQueue, opNumber);
System.out.println("LoopQueue, time: " + lpQueue + " s");
System.out.println("运行时间相差倍数为times = " + (int) (simQueue / lpQueue));
}
}
测试结果:
针对运行环境的不同,可能测试结果会有些出处,但是运行时间的差异已经显现无疑