一、定义
队列(queue)—— 是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。先进先出(First In First Out),简称 FIFO。允许插入的一端叫队尾(rear),允许删除的一端叫队头(front)。
二、队列的顺序存储结构(循环队列)
1、单队列
(1)与栈不同的是,队列中的数据不总是从数组的 0 下标开始的,移除一些队头 front 的数据后,队头指针会指向一个较高的下标位置,如下图:
(2)同样,添加数据时,队尾的指针 rear 也会向下标大的方向移动。
(3)添加多个数据后,队尾指针很快就移动到数组的最末端,这时候可能移除过数据,队头会有空闲的位置;此时,再添加数据的话,由于队尾不能再向上移动(再向后加,就会产生数组越界的错误),可实际上数组还有空闲的位置,这种现象叫做 “假溢出”,如下图:
为了避免上述情况,我们可以让队尾指针绕回到数组开始的位置,也就是循环队列。如下图:
2、循环队列
把队列的头尾相接的顺序存储结构称为循环队列。为了方便,我们让队头 front 指向队列的第一个元素,队尾 rear 指向队列的最后一个元素的后一个位置。如下图:
(1)队列空的条件:
front = rear;
(2)队列满的条件:
当数组中仅剩一个空闲单元的时候,就认为队列已经满了(此时,rear指向的就是剩余的那个空闲单元)。所以队列满的条件为(rear + 1)% QueueSize == front;
(3)队列的长度(数组中实际的元素个数):
- 当 rear > front 时,此时队列的长度为 rear - front;
- 当 rear < front 时,队列的长度分为两段,一段是 QueueSize - front,另一段是 0 + rear,加在一起,队列的长度为 rear + QueueSize - front;
因此,通用的计算队列长度的公式为:(rear - front + QueueSize)% QueueSize。
循环队列代码实现:
public class CircularQueue {
private Object[] array;
private int maxSize;// 最大容量
private int front;// 队头——指向队列的第一个元素
private int rear;// 队尾——指向队列的最后一个元素的后一个位置
// 创建指定大小的队列
public CircularQueue(int size){
maxSize = size;
array = new Object[maxSize];
front = 0;
rear = 0;
}
//判断队列是否已满(当数组中仅剩一个空闲单元的时候,就认为队列已经满了)
public boolean isFull(){
return (rear + 1) % maxSize == front;
}
//判断队列是否为空
public boolean isEmpty(){
return front == rear;
}
//返回队列的大小
public int size(){
return (rear + maxSize - front) % maxSize;
}
// 添加元素
public void insert(Object element){
if(isFull()){
expand();// 满了,扩容
}
array[rear] = element;// 添加数据
rear = (rear + 1) % maxSize;// rear后移
}
//返回队头数据
public Object peek(){
if(isEmpty()){
throw new RuntimeException("队列为空!");
}
return array[front];
}
// 出队
public Object pop(){
if(isEmpty()){
throw new RuntimeException("队列为空!");
}
Object data = array[front];
front = (front + 1) % maxSize;
return data;
}
/*
* 扩容,扩大一倍(满了才扩容):
* 队列满有两种情况:一种是 rear > front,一种是 rear < front;
* 扩容后要调整rear和front的指向
*/
private void expand(){
Object[] newArray = new Object[maxSize * 2];
if(front == 0){// rear > front
array = Arrays.copyOf(array, maxSize * 2);
}else{// rear < front
// 复制元素
for (int i = front; i < maxSize; i++) {
newArray[i] = array[i];
}
int j = maxSize;
for (int i = 0; i < rear; i++) {
newArray[j++] = array[i];
}
// 恢复front和rear的指向(front没变)
rear = maxSize + rear;
array = newArray;
}
}
public static void main(String[] args) {
CircularQueue queue = new CircularQueue(10);
System.out.println(queue.isEmpty());
System.out.println(queue.isFull());
for (int i = 0; i < 9; i++) {
queue.insert(i + 10);
}
System.out.println(queue.isEmpty());
System.out.println(queue.isFull());
System.out.println(queue.size());
System.out.println(queue.peek());
System.out.println(queue.pop());
System.out.println(queue.peek());
}
}
三、队列的链式存储结构(链队列)
队列的链式存储结构其实就是线性表的单链表,只不过它只能尾进头出而已,简称链队列。
为了方便,将队头指针指向链队列的头结点,队尾指针指向终端结点。空队列时,front 和 rear 都指向头结点。
完整代码实现:
public class LinkQueue<T> {
// 内部类,结点类
private class Node{
private T data;// 数据域
private Node next;// 指针域
public Node(){ }
public Node(T data, Node next){
this.data = data;
this.next = next;
}
}
private Node front;// 队头(指链队列的头结点)
private Node rear;// 队尾(指向终端结点)
private int size;// 队列中元素的数目
// 创建一个空队列,空队列时,front和rear都指向头结点
public LinkQueue(){
Node node = new Node();
node.data = null;
node.next = null;
front = rear = node;
}
// 判空
public boolean isEmpty(){
return front == rear;
}
// 队列的大小
public int size(){
return size;
}
// 入队(插入)
public void insert(T element){
Node newNode = new Node(element, null);// 入队的结点没有后继结点
rear.next = newNode;
rear = newNode; //更改rear的指向
size++;
}
// 出队(删除)
public T remove(){
if(isEmpty()){
throw new RuntimeException("队列为空!");
}else{
Node temp = front.next;
front.next = temp.next;
// 如果队列中只有一个元素,出队后即为空队列,要让rear和front都指向头结点
if(temp.next == null){
rear = front;
}
size--;
return temp.data;
}
}
// 打印
public void display(){
Node node = front.next;
while(node != null){
System.out.print(node.data + " ");
node = node.next;
}
System.out.println();
}
public static void main(String[] args) {
LinkQueue<Integer> queue = new LinkQueue<Integer>();
System.out.println(queue.isEmpty());
for (int i = 0; i < 5; i++) {
queue.insert(i + 100);
}
System.out.println(queue.isEmpty());
System.out.println(queue.size());
queue.display();
System.out.println(queue.remove());
queue.display();
}
}
四、优先级队列
优先级队列(priority queue)是比栈和队列更专用的数据结构,在优先级队列中,数据项按照关键字进行排序,关键字最小(或者最大)的数据项往往在队列的最前面,而数据项在插入的时候都会插入到合适的位置以确保队列的有序。
优先级队列是 0 个或多个元素的集合,每个元素都有一个优先权。优先级队列常用的操作是插入和删除。对于优先权相同的元素,可按先进先出次序处理。
这里我们用数组实现优先级队列,声明为 int 类型的数组,关键字是数组里面的元素,在插入数据时按照从大到小的顺序排列,也就是越小的元素优先级越高(最小关键字的数据项总是在数据的最高下标值处,而最大关键字的数据项总是在下标值为 0 的位置上)。
代码如下:
public class PriorityQueue {
private int maxSize;// 最大容量
private int[] queueArray;
private int nItems;// 队列中的数据项数
// 创建指定大小的数组,用以存放队列元素(队列为空)
public PriorityQueue(int size){
maxSize = size;
queueArray = new int[maxSize];
nItems = 0;
}
// 插入
public void insert(int data){
if(nItems == 0){// 空队列,从 0 开始插入
queueArray[nItems++] = data;
}else{
int i = nItems - 1;
// 插入排序,按从大到小的顺序,数据越小对应的下标越大
while(i >= 0 && data > queueArray[i]){
queueArray[i + 1] = queueArray[i];
i--;
}
queueArray[i + 1] = data;// 插入数据
nItems++;
}
}
// 删除
public int remove(){
return queueArray[--nItems];
}
// 打印
public void display(){
for (int i = 0; i < nItems; i++) {
System.out.print(queueArray[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
PriorityQueue queue = new PriorityQueue(10);
queue.insert(13);
queue.insert(33);
queue.insert(56);
queue.insert(24);
queue.insert(8);
queue.display();
queue.remove();
queue.display();
}
}