队列及其实现
什么是队列
具有一定操作约束的线性表。插入和删除操作,只能在一端插入,另一端删除。
数据插入称之为入队(addQ),数据删除称之为出队(deleteQ),队列最重要的特征就是先进先出(FIFO)。生活中有很多跟队列相关的例子,例如超市排队。
队列的抽象数据类型描述
与队列相关的操作主要包括以下几种:
- 创建队列:生成长度为 size 的空队列。
- 判断队列是否满了。
- 判断队列是否为空。
- 将数据元素插入到队列中。
- 将数据元素从队列中删除。
队列的顺序存储实现
队列的顺序存储结构通常由一个一维数组和一个记录队列头元素位置的变量front以及一个记录队列尾元素位置的变量rear组成。
public class SeqQueue<T> implements Queue<T> {
private T elementData[];
private int front, rear;
}
如下图所示,用顺序存储实现队列,由于数组的元素从 0 开始,所以 front 和 rear 同时指向 -1 这个位置,添加 Job1,rear 往后移动一个位置,删除 Job1 ,front 往后移动一个位置。当队列满了的时候,就无法添加元素了,但是很明显就能发现,此时之前删除的位置还是空的,队列中还有位置,只是无法添加而已,这样的结构会造成空间浪费,我们需要用循环结构来解决。
循环队列的机构如下图所示。循环结构中,front 和 rear 开始时同时指向 0 这个位置,之后,每一次入队,rear 向着顺时针方向移动一个位置,每一次出队列,front 向顺时针方向移动一个位置。那么这里就有个问题:队列空和满的判别条件是什么?队列空和满的时候,front=rear
,那么就造成无法判断队列空还是满了。那么要如何解决呢?这里提供两个解决方法:
- 使用额外标记: Size或者tag 。size 用来记录当前元素的个数,当你加入一个元素的时候,size 加 1,删除一个元素的时候,size减1,所以只要根据size是0还是n就知道是空还是满的。tag (0,1)标记,添加一个元素,tag=1,删除一个元素 tag=0,当我们想判断队列是满还是空时,只要判断 tag 的值就知道最后一次操作是添加还是删除。
- 仅使用n-1个数组空间。
我们采用第二种方案,使用求余函数来查看列队是否已满。
(rear + 1) % size == front
看看具体的实现代码:
package leetcode.editor.datastructure.queue;
import java.io.Serializable;
public class SeqQueue<T> implements Queue<T>, Serializable {
private final static int DEAFULT_SIZE = 10;
private T elementData[];
private int front, rear;
private int size;
public SeqQueue() {
elementData = (T[]) new Object[DEAFULT_SIZE];
front = 0;
rear = 0;
}
public SeqQueue(int size) {
this.size = size;
elementData = (T[]) new Object[size];
front = 0;
rear = 0;
}
public void add(T data) {
if ((rear + 1) % this.elementData.length == front) {
System.out.println("队列已满");
return;
}
rear = (rear + 1) % this.elementData.length;
elementData[rear] = data;
}
public boolean isEmpty() {
return this.elementData.length == 0;
}
public T delete() {
if (front == rear) {
System.out.println("队列为空");
return null;
} else {
front = (front + 1) % this.elementData.length;
return elementData[front];
}
}
}
队列的链式存储实现
队列的链式存储结构也可以用一个单链表实现。插入和删除操作分别在链表的两头进行;队列指针front和rear应该分别指向链表的表头和表尾。
整个队列的结构如下图所示:
与顺序结构不同的是,链式存储实现的队列,出队需要在表头进行,因为是单向链表,如果在表尾进行删除操作,我们无法知道前一个元素是多少。因此入队和出队操作为:
public class LinkedQueue<T> implements Queue<T> {
private Node<T> front; //指向队头节点
private Node<T> rear; //指向队尾节点
public LinkedQueue() {
this.front = null;
this.rear = null;
}
@Override
public void add(T data) {
Node<T> node = new Node<>(data, null);
if (this.front == null) {//空队列插入
this.front = node;
} else {//非空队列,尾部插入
this.rear.next = node;
}
this.rear = node;
}
@Override
public boolean isEmpty() {
return front == null && rear == null;
}
@Override
public T delete() {
Node<T> frontCell;
T frontElem;
if (this.front == null) {
System.out.println("队列为空");
return null;
}
frontCell = front;
if (front == rear) //若队列只有一个元素
front = rear = null; //删除后队列置为空
else
front = front.next();//front移动到下一个元素
frontElem = frontCell.data;
return frontElem;
}
}
public class Node<T> {
public T data;
public Node<T> next;
public Node(T data) {
this.data = data;
next = null;
}
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public Node<T> next() {
return this.next;
}
}
参考:浙江大学陈越老师的数据结构课程