一、队列定义
队列 (Queue) 是一种先进先出FIFO (First in First Out) 的线性存储结构。
队列的特殊性:
1. 元素只能从队列的队尾进入,从队头出去
2. 队列中各个元素的进出必须遵循“先进先出”的原则,即最先入队的元素必须最先出队
二、队列类型
1. 顺序队列
顺序队列指的是用顺序表模拟实现的队列存储结构。在编程语言中一般采用数组实现顺序表。
初始化:定义长度为n的数组,定义两个变量 (front和rear) 分别记录队头和队尾的下标。初始化时front和rear都指向第0个位置。
入队:存储新元素,并将rear + 1。当front等于0,rear等于n时,表示队列已满。
出队:废弃当前第一个元素,并将front + 1。当front的值和rear相等时,表示队列已空。
缺陷:
顺序队列前面的空闲空间无法再被使用,造成空间浪费
当顺序队列移动至顺序表尾部时,即便顺序表中有空闲空间,新元素也无法成功入队,一般称这种现象称为“假溢出”
解决办法:可进行数据搬移,重新利用空闲空间
但这样会增加时间复杂度,效率较低。循环队列能更有效的利用队列空间,且不需要移动数据。
代码:
public class ArrayQueue<T> {
// 存储数据的数组
private final T[] items;
// 数组容量
private final int n;
private int size = 0;
// front 记录队头索引 rear 记录队尾索
private int front = 0;
private int rear = 0;
// 申请一个指定容量的队列
public ArrayQueue(int capacity) {
items = (T[]) new Object[capacity];
n = capacity;
}
// 入队
public boolean enqueue(T item) {
// 队满
if (front == 0 && rear == n) {
return false;
}
// 数据搬移
if (front != 0 && rear == n) {
for (int i = front; i < rear; i++) {
items[i - front] = items[i];
}
rear = rear - front;
front = 0;
}
// 将数据加入队列
items[rear] = item;
rear++;
size++;
return true;
}
// 出队
public T dequeue() {
if (front == rear)
return null;
T result = items[front];
front++;
size--;
return result;
}
}
2. 循环队列
所谓循环队列,本质仍是用顺序表模拟实现队列,只不过在具体实现的过程中,会将顺序表想象成首尾相连的环状表来用。
在循环队列中,末尾元素的下一个元素不是数组外,而是数组的头元素。这样就能够再次使用front之前的存储空间了。
初始化:定义长度为n的数组,定义两个变量 (front和rear) 分别记录队头和队尾的下标。初始化时front和rear都指向第0个位置。
入队:存储新元素,rear = rear + 1,考虑到rear在n - 1处时再加1会超过范围 (数组长度为n,rear的范围为0 ~ n - 1),所以需要对rear + 1 模运算,以防其超过范围。即rear = (rear + 1) % n
循环队列队满时front == rear,这和队空的条件一样,无法区分队空还是队满。为了解决该冲突,有三种方法:
① 当front和rear之间剩余一个空闲空间时,就视为队满
即rear + 1 = front则队满。考虑到rear在n - 1处时再加1会超过范围,所以需要对rear + 1 模运算,以防其超过范围。队满条件最终为 (rear + 1) % n = front
② 记录元素个数,以此来判断队满或队空。size = 0,入队size + 1,出队size - 1。或者直接取:
rear >= front:元素个数为size = rear - front
rear < front:元素个数为size = front - rear = n - (rear - front) = rear - front + n;
综合一下,元素个数为size = (rear - front + n) % n
③ 定义一个标识flag,入队flag = 1,出队flag = 0。front == rear且flag == 1时队满;front == rear且flag == 0时队空
出队:废弃当前第一个元素,并将front + 1。当front的值和rear相等时,表示队列已空。
代码:
public class LoopArrayQueue<T> {
// 存储数据的数组
private final T[] items;
// 数组容量
private final int n;
private int size = 0;
private int flag = 0;
// front记录队头索引 rear记录队尾索引
private int front = 0;
private int rear = 0;
// 申请一个指定容量的队列
public LoopArrayQueue(int capacity){
items = (T[]) new Object[capacity];
n = capacity;
}
// 入队
public boolean enqueue(T item){
// 判断队满
// if ((rear + 1) % n == front) return false;
// if (size == n) return false;
if (front == rear && flag == 1) return false;
items[rear] = item;
rear = (rear + 1) % n;
size++;
flag = 1;
return true;
}
// 出队
public T dequeue(){
// 判断队空
// if(front == rear) return null;
// if (size == 0) return null;
if (front == rear && flag == 0) return null;
T result = items[front];
front = (front + 1) % n;
size--;
flag = 0;
return result;
}
}
3. 链式队列
链式队列,简称链队列,即使用链表实现的队列存储结构。链队列采用链式存储结构保存数据元素,允许添加无限多个数据元素,不会出现列满的问题。
入队:创建一个新节点,将原rear节点的next指向新节点,再将rear指向新节点
出队:将原front节点指向原front节点的next节点
代码:
public class LinkedQueue<T> {
// 定义一个节点类
private class Node{
T value;
Node next;
}
// 记录队列元素个数
private int size = 0;
// front指向队头结点 rear指向队尾节点
private Node front;
private Node rear;
// 申请一个队列
public LinkedQueue(){}
// 入队
public boolean enqueue(T item){
Node newNode = new Node();
newNode.value = item;
if (size == 0) front = newNode;
else rear.next = newNode;
rear = newNode;
size++;
return true;
}
// 出队
public T dequeue(){
if(size == 0) return null;
if(size == 1) rear = null;
T result = front.value;
front = front.next;
size--;
return result;
}
}
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情