一、队列(先进先出)
只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头
Queue是个接口,底层通过链表实现,实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。
导包:
import java.util.Queue;
import java.util.LinkedList;
public static void main(String[] args) {
Queue<Integer> queue=new LinkedList<>();
//入队列
queue.offer(1);
queue.offer(3);
queue.offer(5);
//出队列
System.out.println(queue.poll());
//获取队头元素 3
System.out.println(queue.peek());
//获取队列中有效元素个数
System.out.println(queue.size());
//判断队列是否为空
if(!queue.isEmpty()){
System.out.println("队列不为空");
}
方法 功能
boolean offer(E e) 入队列
E poll() 出队列
peek() 获取队头元素
int size() 获取队列中有效元素个数
boolean isEmpty() 检测队列是否为空
双端队列
//双端队列
Deque<Integer> queue2 =new LinkedList<>();
双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。 那就说明元素可以从队头出队和入队,也可以从队尾出队和入队
二、链表模拟实现队列
链式结构实现队列(链表)
优点:
- 动态扩容:不需要预先分配大小,按需创建节点,节省空间。
- 没有溢出问题:只要系统有内存,就可以一直添加元素。
- 更适合频繁插入删除的场景。
缺点:
- 访问速度慢:节点可能分布在不连续的内存中,不利于缓存优化。
- 实现稍复杂:需要维护头尾指针,处理节点的连接与释放。


import java.util.Scanner;
import java.util.Queue;
import java.util.LinkedList;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Queue<Integer>queue=new LinkedList<>();
int n=in.nextInt();
for(int i=0;i<n;i++){
String a=in.next();
if(a.equals("push")){
int num=in.nextInt();
queue.offer(num);
}else if(a.equals("pop")){
if(queue.isEmpty()){
System.out.println("error");
}else{
Integer x=queue.poll();
System.out.println(x);
}
}else{
if(queue.isEmpty()){
System.out.println("error");
}else{
Integer x=queue.peek();
System.out.println(x);
}
}
}
}
}
三、循环队列练习题

错误代码
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Queue<Integer> queue=new LinkedList<>();
int n=in.nextInt();
int k=in.nextInt();
//计数器,判断循环队列是否满
int m=0;
for(int i=0;i<k;i++){
String s=in.next();
if(s.equals("push")){
if(m<n){
//m=n队列满了,不能在入队了
int num=in.nextInt();
queue.offer(num);
m++;
}else{
System.out.println("full");
}
}else if(s.equals("front")){
if(queue.isEmpty()){
System.out.println("empty");
}else{
Integer a= queue.peek();
System.out.println(a);
}
}else{
if(queue.isEmpty()){
System.out.println("empty");
}else{
Integer a= queue.poll();
System.out.println(a);
m--;
}
}
}
}
}
但本质上错误的核心问题是:你使用了 LinkedList 来模拟循环队列,而它不是真正的循环结构。
假设 n=1,我们执行如下操作:
push 9 // 入队成功
pop // 出队成功
push -18 // 理应再次入队成功(因为前面出队了一个)
但你的程序输出却是:
full
为什么?
因为你用 m < n 来判断是否可以 push,但你只在 push 时增加 m++,在 pop 时减少 m--。这看起来是对的,但是你没有真正释放空间给下一次 push 使用!
也就是说:
m是一个模拟的容量限制;- 但底层
queue.offer()实际上是无限扩容的; - 所以即使你把
m控制得再好,也无法体现“空间复用”这一循环队列的本质。
而你的代码使用的是 Java 的 Queue<Integer> queue = new LinkedList<>();,这是一个动态扩容、不支持空间复用的数据结构。
但如果你使用 LinkedList,即使你加了 m--,你也无法知道哪些空间是空闲的,导致第二次 push 被误判为 full
必须手动实现循环队列
import java.util.Scanner;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int p = in.nextInt();
int [] data = new int[n];
int front = 0; //队头指针
int rear = 0; //队尾
int count = 0;
for (int i = 0; i < p; i++) {
String a = in.next();
if (a.equals("push")) {
int num = in.nextInt(); //push后面一定是个整数,要放在这个位置,否则遗漏整数占用次数p
if (count == n) {
System.out.println("full");
} else {
//int num = in.nextInt();//在这里报错
data[rear] = num;
rear = (rear + 1) % n;
count++;
}
} else if (a.equals("front")) {
if (count == 0) {
System.out.println("empty");
} else {
System.out.println(data[front]);
}
} else if (a.equals("pop")) {
if (count == 0) {
System.out.println("empty");
} else {
System.out.println(data[front]);
front = (front + 1) % n;
count --;
}
}
}
}
}
实现循环队列核心语句
它们用于在队列中实现“循环移动指针”的效果,即:
- 当
rear指针到达数组末尾时,自动回到数组开头; - 当
front指针到达数组末尾时,也自动回到数组开头
为什么用 % n?
使用模运算 % n 是为了实现指针的“循环”行为:
- 当指针走到数组末尾时(如 index = 2),下一次加 1 应该回到数组起点(index = 0)
- 所以
(index + 1) % n可以保证指针始终在[0, n-1]范围内循环
2233

被折叠的 条评论
为什么被折叠?



