队列和栈相似,是另一种顺序存储元素的线性数据结构。
-
有两个指针 front、rear指向对头 和队尾;
-
队头(front)一端进行删除操作,队尾(rear)一端进行插入操作,是FIFO,即先进先出
-
使用场景:因为队列先进先出的特点,在多线程阻塞队列管理中非常适用。
-
栈与队列的区别:最大差别在于栈是LIFO(后进先出)
-
队列分:顺序队列 、循环队列、链式队列
队列过程:
- 队头和队尾的指针在初始化是都为指向 0
- 当插入一个元素时,rear 加 1;
- 当删除一个元素时,front 减 1;
由此可见:当 front = rear 时,队列为空。
当队列经过新增,有经过删除时。即此时 front = rear 时,队列为空;但是在添加元素时,可能会发生 “假溢出” 现象,如图:
假溢出现象解决方案:
- 使用一个计数器记录队列中元素个数(即队列长度)
- 使用循环队列,人为浪费一个单元,令队满特征为 front = (rear +1)%maxSize
循环队列是改进版的顺序队列,没有“假溢出”现象
在顺序队列中,当rear=front的时候,队列可能是满,也可能是空。
因此循环队列,加入了对 满和空两种情况的判断:
-
队满:添加元素时,rear + 1 指向了 head 的元素。也就是转圈子要碰头了,就认为队列满了。即:front (rear+1)%MAXSIZE
-
队空:删除元素到 head = rear 的时候,我们认为队列空了。rear == front,不一定为0
Java 循环实现
public class ArrayQueue {
Object[] elementData; // 对象数组,队列最多存储a.length-1个对象
int front; // 队首下标
int rear; // 队尾下标
public ArrayQueue() {
this(10); // 调用其他构造方法
}
public ArrayQueue(int size) {
elementData = new Object[size];
front = 0;
rear = 0;
}
// 入队
public boolean push(Object obj) {
if ((rear + 1) % elementData.length == front) { // 关键步骤 判断是否队满
return false;
}
elementData[rear] = obj;
rear = (rear + 1) % elementData.length; // 关键步骤 队尾指针后移
return true;
}
// 出队
public Object pop() {
if (rear == front) { // 关键步骤 判断是否队空
return null;
}
Object obj = elementData[front];
front = (front + 1) % elementData.length; // 关键步骤 队头指针后移
return obj;
}
public static void main(String[] args) {
ArrayQueue q = new ArrayQueue(4);
System.out.println(q.push("张三")); // true
System.out.println(q.push("李斯")); // true
System.out.println(q.push("赵五")); // true
System.out.println(q.push("王一")); // false 无法入队列,队列满
for (int i = 0; i < 4; i++) {
System.out.println(q.pop());
}
/* 循环体输出 :
* 张三
* 李斯
* 赵五
* null
*/
}
}
链式存储结构,限制仅在表头删除和表尾插入的单链表
代码实现
public class LinkQueue<T> {
// 定义一个内部类Node,Node实例代表链队列的节点。
private class Node {
private T data;// 保存节点的数据
private Node next;// 指向下个节点的引用
// 初始化全部属性的构造器
public Node(T data, Node next) {
this.data = data;
this.next = next;
}
}
private Node front;// 保存该链队列的头节点
private Node rear;// 保存该链队列的尾节点
public LinkQueue() {
// 空链队列,front和rear都是null
front = null;
rear = null;
}
public LinkQueue(T element) {
front = new Node(element, null);
// 只有一个节点,front、rear都指向该节点
rear = front;
}
/**
* 入队
*
* @param element
*/
public void offer(T element) {
Node newNode = new Node(element, null);// 创建新节点
// 如果该链队列还是空链队列
if (front == null) {
front = newNode;
rear = front; // 只有一个节点,front、rear都指向该节点
} else {
rear.next = newNode;// 让尾节点的next指向新增的节点
rear = newNode;// 以新节点作为新的尾节点
}
}
/**
* 出队
* @return
*/
public T poll() {
if (front == null ) {
return null;
}
Node oldFront = front;
front = front.next; // 将队头指针更改为指向 下一个节点
oldFront.next = null;
return oldFront.data;
}
public static void main(String[] args) {
LinkQueue<String> q = new LinkQueue<>();
q.offer("张三");
q.offer("李斯");
q.offer("赵五");
q.offer("神兽");
for (int i = 0; i < 5; i++) {
System.out.println(q.poll());
}
}
}
// 输出:
张三
李斯
赵五
神兽
null