一、什么是队列
前言:双端队列先不在此赘述。
(一)队列的定义
队列 (queue) 是只允许在一端(队头 front)进行插入(enqueue)操作,而在另一端(队尾 rear)进行删除(dequeue)操作的线性表。
(二)队列的特点
- 遵循先进先出 (FIFO) 的原则。
- 队列只允许在队尾进行插入,在队头进行删除。
- 一般的,队列的插入操作称为入队 (enqueue),队列的删除操作称为出队 (dequeue)。
二、为什么要用队列
许多应用场景都使用了队列的结构。作为三大基本数据结构之一,我们必须掌握。
三、如何操作队列
队列作为一种抽象数据类型 (ADT),可通过数组和链表来实现。
首先提供的是 Queue 接口,其内部定义了队列的抽象方法。
/**
* 队列接口,遵循先进先出的原则。
*
* @author likezhen
* @version 1.0
*/
public interface Queue<E> {
/**
* 返回队列中存放的数据个数
*
* @return 队列中存放的数据个数
*/
int size();
/**
* 判断是否为空队列
*
* @return 是否为空队列
*/
boolean isEmpty();
/**
* 将数据插入队尾 (rear)
* @param e 待入队的数据
*/
void enqueue(E e);
/**
* 移除并返回队头数据
*
* @return 队头数据(如果为空队列则返回Null)
*/
E dequeue();
/**
* 返回队头数据
*
* @return 队头数据(如果为空队列则返回Null)
*/
E peek();
}
(一)用循环数组实现队列
ArrayQueue 类实现队列接口。因为用数组作为储存容器,所以需要先指明其容量,一旦确定,就不能更改。特别地,在此数组队列中,我们选择记录队头指针 front 和数据个数 size的办法(也可以选择记录队头指针 front 和队尾指针 rear的办法)。实现循环数组的关键在于 % 取余操作。
/**
* 通过数组来实现队列结构
* 提供了 enqueue(), dequeue(), peek()等方法
*
* @author likezhen
* @version 1.0
*/
class ArrayQueue<E> implements Queue<E> {
private E[] data;
private int front; //指向队头的指针
private int size; //数据的个数
private static final int CAPACITY = 10; //默认队列的初始容量为10
public ArrayQueue() {
this(CAPACITY);
}
public ArrayQueue(int capacity) { //可自定义队列的容量
data = (E[]) new Object[capacity];
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void enqueue(E e) throws IllegalStateException {
if (size == data.length) throw new IllegalStateException("队列已满");
int avail = (front + size) % data.length; //实现循环数组的关键操作
data[avail] = e;
size++;
}
@Override
public E dequeue() {
if (isEmpty()) return null;
E answer = data[front];
data[front] = null;
front = (front + 1) % data.length; //实现循环数组的关键操作
size--;
return answer;
}
@Override
public E peek() {
if (isEmpty()) return null;
return data[front];
}
}
测试类中进行了三次入队操作,查看队头元素,后通过遍历拿到出队顺序。
/**
* 用数组实现队列的测试类
*
* @author likezhen
* @version 1.0
*/
public class ArrayQueueApp {
public static void main(String[] args) {
ArrayQueue<Person> arrayQueue = new ArrayQueue<>(3); //空队列,容量为3
arrayQueue.enqueue(new Person("A", 1)); //入队
arrayQueue.enqueue(new Person("B", 2)); //入队
arrayQueue.enqueue(new Person("C", 3)); //入队
System.out.println("--------入队顺序-------\n" +
"Person{name='A', age=1}\n" +
"Person{name='B', age=2}\n" +
"Person{name='C', age=3}");
System.out.println("--------队头元素--------");
System.out.println(arrayQueue.peek());
System.out.println("--------出队顺序--------");
while (!arrayQueue.isEmpty()) {
System.out.println(arrayQueue.peek());
arrayQueue.dequeue();
}
}
}
测试结果:
D:\Java\jdk\bin\java.exe
--------入队顺序-------
Person{name='A', age=1}
Person{name='B', age=2}
Person{name='C', age=3}
--------队头元素--------
Person{name='A', age=1}
--------出队顺序--------
Person{name='A', age=1}
Person{name='B', age=2}
Person{name='C', age=3}
Process finished with exit code 0
(二)用单链表实现队列
LinkedQueue 类实现了队列接口,封装了一个单链表对象。通过调用单链表的各种方法,即可实现链栈的操作。关于单链表的源代码,可参考我之前总结的一篇关于链表的文章——【数据结构与算法】之链表。
队列方法 | 单链表方法 | 描述 |
---|---|---|
size() | list.getSize() | 获取队列(或链表)中的数据个数 |
isEmpty() | list.isEmpty() | 判断是否为空队列(或空链表) |
enqueue(E e) | list.addLast(E e) | 在队尾(或表尾)添加数据 |
dequeue() | list.removeFirst() | 移除队头(或表头)的数据 |
peek() | list.first() | 获取队头(或表头)存放的数据 |
/**
* 通过链表来实现队列结构
* 提供了 enqueue(), dequeue(), peek()等方法
*
* @author likezhen
* @version 1.0
*/
class LinkedQueue<E> implements Queue<E> {
private SinglyLinkedList<E> list = new SinglyLinkedList<>();
@Override
public int size() {
return list.getSize();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public void enqueue(E e) {
list.addLast(e);
}
@Override
public E dequeue() {
return list.removeFirst();
}
@Override
public E peek() {
return list.first();
}
}
入队顺序为 A->B->C,队头数据为A,出队顺序为 A->B->C,符合先进先出的原则。
/**
* 用链表实现队列的测试类
*
* @author likezhen
* @version 1.0
*/
public class LinkedQueueApp {
public static void main(String[] args) {
LinkedQueue<Person> linkedQueue = new LinkedQueue<>();
linkedQueue.enqueue(new Person("A", 1)); //入队
linkedQueue.enqueue(new Person("B", 2)); //入队
linkedQueue.enqueue(new Person("C", 3)); //入队
System.out.println("--------入队顺序-------\n" +
"Person{name='A', age=1}\n" +
"Person{name='B', age=2}\n" +
"Person{name='C', age=3}");
System.out.println("--------队头元素--------");
System.out.println(linkedQueue.peek());
System.out.println("--------出队顺序--------");
while (!linkedQueue.isEmpty()) {
System.out.println(linkedQueue.peek());
linkedQueue.dequeue();
}
}
}
测试结果:
D:\Java\jdk\bin\java.exe
--------入队顺序-------
Person{name='A', age=1}
Person{name='B', age=2}
Person{name='C', age=3}
--------队头元素--------
Person{name='A', age=1}
--------出队顺序--------
Person{name='A', age=1}
Person{name='B', age=2}
Person{name='C', age=3}
Process finished with exit code 0
时间复杂度分析:
队列和栈类似,因为其特殊的结构,所以其入队出队等所有队列操作的(最坏)时间复杂度都为 O(1),与数据量无关。