队列是计算机中常用的一种数据结构,其特点是“先进先出”,即先进入队列的元素先出队列。队列通常用于实现缓存、调度等场景,例如操作系统中的进程调度、Java 中的线程池等。
在队列中,有两个主要概念:
1. 队首(front):允许插入元素和删除元素的一端,称为队列的头部。
2. 队尾(rear):允许插入元素但不允许删除元素的一端,称为队列的尾部。
队列有以下两种实现方式:
1. 基于数组的实现:使用固定长度的数组来存储队列元素。在向队列中添加元素时,需要将已有元素向队尾移动,在删除元素时,需要将所有元素向队首移动。由于数组的长度是固定的,因此向队列中添加新元素时可能会出现“队列已满”的情况。
2. 基于链表的实现:使用链表来存储队列元素。在向队列中添加元素时,只需要将新元素添加到链表尾部。在删除元素时,只需要移除链表头部元素。由于链表的长度是动态的,因此链表实现的队列不存在“队列已满”的情况,但在访问链表的某个位置时需要花费额外的时间。
队列解决的问题主要涉及到如何添加和删除队列中的元素,同时还需要考虑队列中元素的先后顺序。由于队列中的元素只允许按照“先进先出”的方式访问,因此需要使用队首和队尾两个指针来记录队列中元素的位置。对于数组实现的队列来说,添加或删除元素可能需要进行大量的数据移动,因此效率可能比较低;对于链表实现的队列来说,添加或删除元素只需要修改指针,效率相对较高。
此处,我们先使用基于数组的实现方式,后期会出基于链表的实现方式
数组模拟队列——不涉及环形队列
基于数组解决队列问题的方式,也称为“顺序队列”,需要定义两个指针,一个指向队列头部,一个指向队列尾部,同时需要使用一个计数器记录队列中的元素数量。
在向队列中添加元素时,需要将新元素添加到队尾,并将队尾指针向后移动一个位置;在删除元素时,需要将队头指针向后移动一个位置,并将队头位置上的元素弹出,同时将队列中的元素数量减1。
package com.success.day01;
import java.util.Scanner;
public class ArrayQueueDemo {
//使用数组模拟队列
static class ArrayQueue {
//表示数组的最大元素
private int maxSize;
//队列头
private int front;
//队列尾
private int rear;
//创建队列
private int[] arr;
/**
* @param arrMaxSize 队列最大节点数
* @return
* @Description 创建队列的构造器
*/
public ArrayQueue(int arrMaxSize) {
//队列最大节点数
maxSize = arrMaxSize;
//初始化队列
arr = new int[maxSize];
//指向队列头部,分析出front是指向队列头的前一个位置
front = -1;
//指向队列尾部
rear = -1;
}
/**
* @return boolean
* @Description 判断队列是否已满
*/
public boolean isFull() {
return rear == maxSize - 1;
}
/**
* @return boolean
* @Description 判断队列是否为空
*/
public boolean isEmpty() {
return rear == front;
}
/**
* @param n 加入的数值
* @Description 加入队列
*/
public void addQueue(int n) {
if (isFull()) {
System.out.println("对不起,队列已满!!!");
return;
}
//后移,正式指向第一个节点
rear++;
//第一个节点数据填入
arr[rear] = n;
}
/**
* @return int
* @Description 获取队列数据
*/
public int getQueue() {
//判断队列是否为空
if (isEmpty()) {
throw new RuntimeException("对不起,队列为空,不能取出数据!!!");
}
//font后移,指向第一个节点
front++;
//返回第一个节点的数据
return arr[front];
}
// 显示队列的所有数据
public void showQueue() {
// 遍历
if (isEmpty()) {
System.out.println("队列空的,没有数据~~");
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.printf("arr[%d]=%d\n", i, arr[i]);
}
}
// 显示队列的头数据, 注意不是取出数据
public int headQueue() {
// 判断
if (isEmpty()) {
throw new RuntimeException("对不起,队列空的,没有数据!!!");
}
return arr[front + 1];
}
}
/**
* @param args 形参
* @Description 测试模块
*/
public static void main(String[] args) {
ArrayQueue queue = new ArrayQueue(3);
char key = ' '; //接收用户输入
Scanner scanner = new Scanner(System.in);//
boolean loop = true;
//输出一个菜单
while (loop) {
System.out.println("s(show): 显示队列");
System.out.println("e(exit): 退出程序");
System.out.println("a(add): 添加数据到队列");
System.out.println("g(get): 从队列取出数据");
System.out.println("h(head): 查看队列头的数据");
key = scanner.next().charAt(0);//接收一个字符
switch (key) {
case 's':
queue.showQueue();
break;
case 'a':
System.out.println("输出一个数");
int value = scanner.nextInt();
queue.addQueue(value);
break;
case 'g': //取出数据
try {
int res = queue.getQueue();
System.out.printf("取出的数据是%d\n", res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h': //查看队列头的数据
try {
int res = queue.headQueue();
System.out.printf("队列头的数据是%d\n", res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'e': //退出
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出已退出!!!");
}
}
数组模拟队列——环形队列
环形队列是一种特殊的队列数据结构,它的底层实现使用了数组,和一般的队列一样,它具有先进先出(FIFO)的特性,但其和一般的队列不同的地方在于,环形队列的队尾可以追上队头,形成一个环形结构。
通常情况下,环形队列会预留一个位置,作为队列满和队列空的判断标志,其中一个指针指向队头,另一个指针指向队尾。在入队操作时,先判断是否已满;如果已满,则表示队列已满,不能再添加新元素;如果未满,则将元素添加到队尾,并将队尾指针向前移动一个位置;在出队操作时,先判断是否为空;如果为空,则表示队列为空,没有元素可以删除;如果不为空,则删除队头元素,并将队头指针向前移动一个位置。
由于环形队列不需要进行元素的搬迁,因此其性能较好,适用于在固定大小的缓冲区中存储数据。常见的应用场景包括操作系统内存管理、网络协议栈等。
package com.success.day01;
import java.util.Scanner;
public class CircleArrayQueueDemo {
public static void main(String[] args) {
System.out.println("测试数组模拟环形队列的案例~~~");
// 创建一个环形队列
CircleArray queue = new CircleArray(4); //说明设置4, 其队列的有效数据最大是3
char key = ' '; // 接收用户输入
Scanner scanner = new Scanner(System.in);//
boolean loop = true;
// 输出一个菜单
while (loop) {
System.out.println("s(show): 显示队列");
System.out.println("e(exit): 退出程序");
System.out.println("a(add): 添加数据到队列");
System.out.println("g(get): 从队列取出数据");
System.out.println("h(head): 查看队列头的数据");
key = scanner.next().charAt(0);// 接收一个字符
switch (key) {
case 's':
queue.showQueue();
break;
case 'a':
System.out.println("输出一个数");
int value = scanner.nextInt();
queue.addQueue(value);
break;
case 'g': // 取出数据
try {
int res = queue.getQueue();
System.out.printf("取出的数据是%d\n", res);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case 'h': // 查看队列头的数据
try {
int res = queue.headQueue();
System.out.printf("队列头的数据是%d\n", res);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case 'e': // 退出
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出~~");
}
}
/**
* @Description 环形队列
*/
class CircleArray {
// 表示数组的最大容量
private int maxSize;
//front 就指向队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素
private int front;
//rear 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定.
private int rear;
//初始化队列
private int[] arr;
/**
* @param arrMaxSize 队列最大节点数(比实际多一个,作为转换节点)
* @return
* @Description 构造队列
*/
public CircleArray(int arrMaxSize) {
maxSize = arrMaxSize;
arr = new int[maxSize];
}
/**
* @return boolean
* @Description 判断队列是否满
*/
public boolean isFull() {
return (rear + 1) % maxSize == front;
}
/**
* @return boolean
* @Description 判断队列是否为空
*/
public boolean isEmpty() {
return rear == front;
}
/**
* @param n 需要填充的数据
* @Description 添加数据到队列
*/
public void addQueue(int n) {
// 判断队列是否满
if (isFull()) {
System.out.println("队列满,不能加入数据~");
return;
}
//直接将数据加入
arr[rear] = n;
//将 rear 后移, 这里必须考虑取模
rear = (rear + 1) % maxSize;
}
/**
* @return int
* @Description 获取队列的数据, 移出队列
*/
public int getQueue() {
// 判断队列是否空
if (isEmpty()) {
// 通过抛出异常
throw new RuntimeException("队列空,不能取数据");
}
// 这里需要分析出 front是指向队列的第一个元素
// 1. 先把 front 对应的值保留到一个临时变量
// 2. 将 front 后移, 考虑取模
// 3. 将临时保存的变量返回
int value = arr[front];
front = (front + 1) % maxSize;
return value;
}
/**
* @Description 显示队列的所有数据
*/
public void showQueue() {
// 遍历
if (isEmpty()) {
System.out.println("队列空的,没有数据~~");
return;
}
// 思路:从front开始遍历,遍历多少个元素
//此处就可以不输出已经剔除的元素,实际不是真的删除,而是不再输出
for (int i = front; i < front + size(); i++) {
System.out.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]);
}
}
/**
* @return int
* @Description 求出当前队列有效数据的个数
*/
public int size() {
// rear = 2
// front = 1
// maxSize = 3
return (rear + maxSize - front) % maxSize;
}
// 显示队列的头数据, 注意不是取出数据
public int headQueue() {
// 判断
if (isEmpty()) {
throw new RuntimeException("队列空的,没有数据~~");
}
return arr[front];
}
}
最后,在这里,向尚硅谷的老师说一声谢谢,受益匪浅!
然后,然后一段话送给自己,也送给屏幕前的各位:
亲爱的自己,我想对你说声感谢:
感谢你一直以来的努力和坚持,让自己不停地成长和进步。感谢你勇敢面对生活中的挑战和困难,从中不断吸取经验和教训,让自己更加坚强和有信心。感谢你时刻保持着积极向上的心态,为自己和身边的人带来正能量和力量。
你的努力和付出,也让我今天成为了更好的自己,让我可以为你所做的一切感到骄傲和自豪。希望你能继续保持这份努力和进取心,为自己的梦想和目标奋斗不息,成就更加出色的未来。
愿我们一直保持着这份感恩和自由的心态,共同迎接未来的挑战和机遇。