1 概述
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out)的线性表,简称FIFO,允许插入的一端称为队尾,允许删除的一端称为队头。
2 抽象数据类型
ADT 队列(Queue)
Data:同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation:
init(int maxSize):初始化操作,建立一个空栈;
destroy():若栈存在,则销毁它;
clear():清空栈;
isEmpty():若栈为空,返回true,否则返回false;
getHead():若队列存在且非空,返回队头元素;
insert(DataType e):若队列存在,插入新元素到队尾;
delete():若队列存在,删除队头元素;
length():返回队列的元素个数;
endADT
顺序存储的队列与线性表的顺序存储结构完全相同,但有一些问题:出队列的时候因为要从队头出,即下标为0的元素出,因此出队列时要移动其后的所有元素。因此通常,会使用循环队列来实现队列。
头尾相接的顺序存储结构的队列,被称为循环队列。
3 顺序存储结构
3.1 队列满、队列空判断和队列实际元素数量
循环队列引入了两个指针:
(1) rear:指向队尾元素的下一个位置;
(2) front:指向队头元素;
同时,队列还有一个maxSize属性,表示该队列的最大长度。
假设有一个最大长度为5的队列,初始化时,队列如下所示:
这时,入队a1:
接着入队a2、a3、a4:
按照我们对队列的定义:rear永远指向一个空的元素空间,可知,此时队列已满,这时,rear + 1 = maxSize。
这时,我们把a1和a2分别出队列:
再将a5和a6入队列:
根据队列定义,此时队列也已满,rear实际上比front大一圈,但从下标上看,只比front小1,也即rear + 1 = front,但如何表现大一圈呢?直接让(rear+1) % maxSize = front即可。
而上文提到,在第一圈时,rear + 1 = maxSize也表示队列满了,而这种情况下,使用(rear + 1) % maxSize = front也适用。
因此,队列满的条件是:
(rear + 1) % maxSize == front
同理,队列长度的计算公式是:
(rear - front + maxSize) % maxSize
队列为空则很简单,当初始化时,rear = front = 0,在上述操作的基础上,我们继续出队a3、a4,变成如下:
继续出队a5:
出队a6:
此时队列为空,仍然是rear = front,因此,队列为空时:
rear == front
3.2 入队操作
在3.1中已描述入队操作,即在队尾插入元素,并把rear往后挪一位即可:
/**
* 入队列
*
* @param val 待进入队列的元素
* @throws RuntimeException 当队列已满时抛出异常
* @author Korbin
* @date 2023-01-17 15:47:13
**/
public void insert(T val) {
if ((rear + 1) % maxSize == front) {
// 队列已满
throw new RuntimeException("fulled");
}
data[rear] = val;
rear = (rear + 1) % maxSize;
}
3.3 出队操作
在3.1中已描述出队操作,即把front指向的元素去除,并把front往后挪一位即可:
/**
* 出队列
*
* @return 出队列的元素
* @throws RuntimeException 队列为空时抛出异常
* @author Korbin
* @date 2023-01-17 15:49:25
**/
public T delete() {
if (isEmpty()) {
// 队列为空
throw new RuntimeException("empty queue");
}
T result = data[front];
data[front] = null;
front = (front + 1) % maxSize;
return result;
}
3.4 完整代码
/**
* 队列
*
* @author Korbin
* @date 2023-01-17 11:54:53
**/
public class Queue<T> {
/**
* 数据
**/
private T[] data;
/**
* 头指针
**/
private int front;
/**
* 队列最大元素数量
**/
private int maxSize;
/**
* 尾指针,若队列不空,指向队列尾元素下一个位置
**/
private int rear;
/**
* 清空队列
*
* @author Korbin
* @date 2023-01-17 15:50:36
**/
public void clear() {
init(maxSize);
}
/**
* 出队列
*
* @return 出队列的元素
* @throws RuntimeException 队列为空时抛出异常
* @author Korbin
* @date 2023-01-17 15:49:25
**/
public T delete() {
if (isEmpty()) {
// 队列为空
throw new RuntimeException("empty queue");
}
T result = data[front];
data[front] = null;
front = (front + 1) % maxSize;
return result;
}
/**
* 返回队头元素
*
* @return 队头元素
* @throws RuntimeException 队列为空时抛出异常
* @author Korbin
* @date 2023-01-17 15:52:33
**/
public T getHead() {
if (isEmpty()) {
// 队列为空
throw new RuntimeException("empty queue");
}
return data[front];
}
/**
* 初始化
*
* @author Korbin
* @date 2023-01-17 15:43:34
**/
@SuppressWarnings("unchecked")
public void init(int maxSize) {
this.maxSize = maxSize;
front = 0;
rear = 0;
data = (T[]) new Object[maxSize];
}
/**
* 入队列
*
* @param val 待进入队列的元素
* @throws RuntimeException 当队列已满时抛出异常
* @author Korbin
* @date 2023-01-17 15:47:13
**/
public void insert(T val) {
if ((rear + 1) % maxSize == front) {
// 队列已满
throw new RuntimeException("fulled");
}
data[rear] = val;
rear = (rear + 1) % maxSize;
}
/**
* 判断队列是否为空,为空返回true,否则返回false
*
* @return 为空返回true,否则返回false
* @author Korbin
* @date 2023-01-17 15:51:32
**/
public boolean isEmpty() {
return front == rear;
}
/**
* 队列实际元素数量
*
* @return 实际元素数量
* @author Korbin
* @date 2023-01-17 15:44:51
**/
public int length() {
return (rear - front + maxSize) % maxSize;
}
}
4 链式存储结构
4.1 概述
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队,为了操作上的方便,我们将队头指针指向单链表的头结点,队尾指针指向终端结点:
空队列时,front和rear都指向头结点:
因而,链队的结点结构与单链表一致,参考《大话数据结构-线性表》{:target="_blank"}。
4.2 入队
已知,当链队为空时,rear指向头结点,当链队不为空时,rear指向终端结点,因此入队操作实现很简单:将原rear的后继结点改为待插入的结点,插入后这个“待插入的结点”变为终端结点,因rear是指向终端结点的,所以令rear指向这个“待插入的结点”,就完成了插入:
/**
* 入队列
*
* @param val 待进入队列的元素
* @throws RuntimeException 当队列已满时抛出异常
* @author Korbin
* @date 2023-01-17 15:47:13
**/
public void insert(LinkListNode<T> val) {
rear.setNext(val);
rear = val;
length++;
}
4.3 出队
front指向头结点,头结点是不存储数据的,因此第一个结点应为front.next,出队,即取出front.next,并让front.next指向front.next.next即可。
在出队时,如果只有一个结点时,rear指向了front.next,如果把front.next取出来后,队列为空队列,此时,应把rear也指向头结点,即rear = front:
/**
* 出队列
*
* @return 出队列的元素
* @throws RuntimeException 队列为空时抛出异常
* @author Korbin
* @date 2023-01-17 15:49:25
**/
public LinkListNode<T> delete() {
if (isEmpty()) {
// 队列为空
throw new RuntimeException("empty queue");
}
LinkListNode<T> node = front.getNext();
front.setNext(node.getNext());
if (rear.equals(node)) {
// 如果删除的结点与队尾结点相同,则表示队列中只有一个结点,删除后队列为空,应让rear指向头结点
rear = front;
}
length--;
return node;
}
4.4 完整代码
/**
* 链队列
*
* @author Korbin
* @date 2023-01-17 16:43:35
**/
public class LinkQueue<T> {
/**
* 头指针
**/
private LinkListNode<T> front;
/**
* 结点数量
**/
private int length;
/**
* 尾指针,若队列不空,指向队列尾元素下一个位置
**/
private LinkListNode<T> rear;
/**
* 清空队列
*
* @author Korbin
* @date 2023-01-17 15:50:36
**/
public void clear() {
init();
}
/**
* 出队列
*
* @return 出队列的元素
* @throws RuntimeException 队列为空时抛出异常
* @author Korbin
* @date 2023-01-17 15:49:25
**/
public LinkListNode<T> delete() {
if (isEmpty()) {
// 队列为空
throw new RuntimeException("empty queue");
}
LinkListNode<T> node = front.getNext();
front.setNext(node.getNext());
if (rear.equals(node)) {
// 如果删除的结点与队尾结点相同,则表示队列中只有一个结点,删除后队列为空,应让rear指向头结点
rear = front;
}
length--;
return node;
}
/**
* 返回队头元素
*
* @return 队头元素
* @throws RuntimeException 队列为空时抛出异常
* @author Korbin
* @date 2023-01-17 15:52:33
**/
public LinkListNode<T> getHead() {
if (isEmpty()) {
// 队列为空
throw new RuntimeException("empty queue");
}
return front.getNext();
}
/**
* 初始化
*
* @author Korbin
* @date 2023-01-17 15:43:34
**/
public void init() {
front = new LinkListNode<>();
rear = front;
}
/**
* 入队列
*
* @param val 待进入队列的元素
* @throws RuntimeException 当队列已满时抛出异常
* @author Korbin
* @date 2023-01-17 15:47:13
**/
public void insert(LinkListNode<T> val) {
rear.setNext(val);
rear = val;
length++;
}
/**
* 判断队列是否为空,为空返回true,否则返回false
*
* @return 为空返回true,否则返回false
* @author Korbin
* @date 2023-01-17 15:51:32
**/
public boolean isEmpty() {
return null != front && null != rear && front.getId().equals(rear.getId());
}
/**
* 队列实际元素数量
*
* @return 实际元素数量
* @author Korbin
* @date 2023-01-17 15:44:51
**/
public int length() {
return length;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (null != front && null != rear) {
builder.append("[front:{id=").append(front.getId()).append("},");
LinkListNode<T> nextNode = front.getNext();
while (null != nextNode) {
builder.append(nextNode).append(",");
nextNode = nextNode.getNext();
}
builder.append("rear:{id=").append(rear.getId()).append("}]");
}
return builder.toString();
}
}