一、队列是什么?
这里引入百度百科的解释:
- 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。
- 进行插入操作的端称为队尾,进行删除操作的端称为队头。
- 通俗的理解:一种先进先出的数据结构
队列上溢:队列的元素数量超出队列的容量
队列下溢:对空队列执行出队操作
二、队列的接口
代码如下(示例):
public interface Queue<E> {
void enqueue(E e);
E dequeue();
E getFront();
int getSize();
boolean isEmpty();
}
三、实现队列的接口
1.通过动态数组实现
动态数组的底层实现可参考:
https://blog.csdn.net/GGB__/article/details/120248300
代码如下(示例):
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 void enqueue(E e) {
array.addLast(e);
}
@Override
public E dequeue() {
return array.removeFirst();
}
@Override
public E getFront() {
return array.get(0);
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
public int getCapacity() {
return array.getCapacity();
}
@Override
public String toString() {
return "出队ArrayQueue:" + array + "入队";
}
}
2.通过两个栈实现
栈的底层实现可参考:
https://blog.csdn.net/GGB__/article/details/120250106
实现思想:
-
利用栈先进后出的性质
-
两个栈 一个栈负责处理入队
-
另一个栈负责处理出队
-
出队操作时:
-
将负责入队操作的栈执行出栈操作
-
将出栈的元素放置于负责出队操作的栈中
-
直至负责入队操作的栈为空,再对负责出队操作的栈执行出栈操作,进而实现出队操作。
-
入队操作时:
-
如果负责出队操作的栈不为空,则先将该栈的所有元素执行出栈,存放在负责入队操作的栈中,再执行入队操作
代码如下(示例):
public class ArrayStack2<E> {
private ArrayStack<E> stack1 = new ArrayStack<>();
private ArrayStack<E> stack2 = new ArrayStack<>();
public void push(E e) {
while (!stack2.isEmpty()) {
stack1.push(stack2.pop());
}
stack1.push(e);
}
;
public E pop() {
if (isEmpty()) {
return null;
}
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
return stack2.pop();
}
;
public E getFront() {
if (isEmpty()) {
return null;
}
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
return stack2.pop();
}
public boolean isEmpty() {
return stack1.isEmpty() && stack2.isEmpty();
}
public E peek() {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
return stack2.peek();
}
@Override
public String toString() {
while (!stack2.isEmpty()) {
stack1.push(stack2.pop());
}
return "ArrayStack2:出队" + stack1 + "入队";
}
}
3.测试
代码如下(示例):
public static void main(String[] args) {
Queue<Integer> arrayQueue = new ArrayQueue<>(10);
for (int i = 0; i < 10; i++) {
arrayQueue.enqueue(i);
}
System.out.println(arrayQueue);
System.out.println(arrayQueue.dequeue());
System.out.println(arrayQueue);
System.out.println(arrayQueue.getFront());
System.out.println(arrayQueue);
}
测试结果如下(示例):
出队ArrayQueue:Array:size=10, capacity=10
[0,1,2,3,4,5,6,7,8,9]入队
0
出队ArrayQueue:Array:size=9, capacity=10
[1,2,3,4,5,6,7,8,9]入队
1
出队ArrayQueue:Array:size=9, capacity=10
[1,2,3,4,5,6,7,8,9]入队
四、循环队列
简单介绍
- 循环队列就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用。
- 在循环队列结构中,当存储空间的最后一个位置已被使用而再要进入队运算时,只需要存储空间的第一个位置空闲,便可将元素加入到第一个位置,此时的front = (front+1)%capacity 即将存储空间的第一个位置作为队尾。
- 循环队列可以更简单防止伪溢出的发生,但队列大小是固定的。
在循环队列中,当队列为空时,有front=tail,而当所有队列空间全占满时,也有front=tail。
为了区别这两种情况,规定循环队列最多只能有capacity-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。
因此,队列判空的条件是front=tail,而队列判满的条件是front=(tail+1)%capacity.
一张图了解循环队列
代码实现:
package com.lingo.queue;
/**
* 这里循环队列的尾指针空出一个位置(以空间换时间的思想)
*
* @param <E>
*/
public class LoopQueueT<E> implements Queue<E> {
//底层数组
private E[] data;
//头指针
private int front;
//尾指针
private int tail;
private int size;
public LoopQueueT(int capacity) {
data = (E[]) new Object[capacity + 1];
front = 0;
tail = 0;
size = 0;
}
public LoopQueueT() {
this(10);
}
@Override
public void enqueue(E e) {
if ((tail + 1) % data.length == front) {
reSize(data.length * 2);
}
data[tail] = e;
//是预防数组后无元素空间,前面有空间
tail = (tail + 1) % data.length;
size++;
}
@Override
public E dequeue() {
if (isEmpty()) {
throw new IllegalArgumentException("队列为空!");
}
E e = data[front];
data[front] = null;
front = (front + 1) % data.length;
size--;
//自动缩容
//这里不能是data.length/2 ==> 如果是size=5 capacity=11 导致无法存留一个空的空间给tail
if (size == data.length / 4 && data.length / 2 != 0) {
reSize(data.length / 2);
}
return e;
}
//采用从尾加从头出
@Override
public E getFront() {
if (isEmpty()) {
throw new IllegalArgumentException("队列为空!");
}
E e = data[front];
return e;
}
@Override
public int getSize() {
return size;
}
public int getCapacity() {
return data.length - 1;
}
/**
* 自动扩容/缩容
*
* @param newCapacity
*/
private void reSize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[(front + i) % data.length];
}
data = newData;
front = 0;
tail = size;
}
@Override
public boolean isEmpty() {
return front == tail;
}
public static void main(String[] args) {
LoopQueueT<Integer> loopQueueT = new LoopQueueT<>();
loopQueueT.enqueue(11);
loopQueueT.enqueue(10);
loopQueueT.enqueue(4);
loopQueueT.enqueue(6);
System.out.println("容量:" + loopQueueT.getCapacity());
System.out.println("出队操作:" + loopQueueT.dequeue());
System.out.println("容量:" + loopQueueT.getCapacity());
System.out.println("出队操作:" + loopQueueT.dequeue());
System.out.println("容量:" + loopQueueT.getCapacity());
System.out.println("出队操作:" + loopQueueT.dequeue());
System.out.println("容量:" + loopQueueT.getCapacity());
}
}
输出结果:
容量:10
出队操作:11
容量:10
出队操作:10
容量:4
出队操作:4
容量:1