0%

循环队列

循环队列:数组队列的优化与实现

循环队列是对数组实现的线性队列的改进,通过 “逻辑上环化” 数组,解决了普通队列因头尾指针单向移动导致的 “假满” 问题,提高了数组空间的利用率。本文详细解析循环队列的原理、判断条件及实现方式。

普通队列的 “假满” 问题

普通队列(基于数组)的头尾指针(frontrear)只能单向移动:

  • 入队时 rear 后移,出队时 front 后移。
  • rear 达到数组末尾(rear = capacity-1)时,即使 front 前有空闲空间,也无法继续入队(称为 “假满”)。

例如:

  • 数组容量为 5,入队 [A,B,C,D] 后,rear=3front=0
  • 出队 AB 后,front=2rear=3(此时数组前 2 个位置空闲)。
  • 若再入队 Erear=4(已满);若继续入队 F,则因 rear 无法移动导致 “假满”,但实际 front 前有空间。

循环队列的核心思想

循环队列通过逻辑上环化数组(即 rear 到达数组末尾后,重新从 0 开始)解决假满问题:

  • 数组的首尾在逻辑上相连(形成环形)。
  • rearfront 移动时,通过取模运算(% capacity)实现 “循环”。

循环队列的关键判断条件

为区分 “队空” 和 “队满”,循环队列通常浪费一个空间(即数组容量为 capacity 时,最多存储 capacity-1 个元素)。

1. 队空条件

  • front == rear 时,队列为空。
  • 含义:头尾指针重合,无元素可出队。

2. 队满条件

  • (rear + 1) % capacity == front 时,队列已满。
  • 含义:rear 向后移动一位(取模后)与 front 重合,此时剩余 1 个空间未使用(用于区分队空)。

3. 元素个数计算

  • 队列中元素数量 = (rear - front + capacity) % capacity
  • 解释:通过 +capacity 避免 rear < front 时出现负数(如 rear=1front=3capacity=5 时,(1-3+5)%5=3,即 3 个元素)。

循环队列的实现(Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class CircularQueue {
private int[] queue; // 存储元素的数组
private int front; // 队头指针(指向第一个元素)
private int rear; // 队尾指针(指向最后一个元素的下一位)
private int capacity; // 数组容量(实际最大存储 capacity-1 个元素)

// 初始化队列
public CircularQueue(int capacity) {
this.capacity = capacity;
queue = new int[capacity];
front = 0;
rear = 0;
}

// 入队
public boolean enqueue(int value) {
if (isFull()) {
System.out.println("队列已满,无法入队");
return false;
}
queue[rear] = value; // 存入元素
rear = (rear + 1) % capacity; // 队尾指针后移(循环)
return true;
}

// 出队
public Integer dequeue() {
if (isEmpty()) {
System.out.println("队列为空,无法出队");
return null;
}
int value = queue[front]; // 取出队头元素
front = (front + 1) % capacity; // 队头指针后移(循环)
return value;
}

// 获取队头元素
public Integer peek() {
if (isEmpty()) {
return null;
}
return queue[front];
}

// 判断队空
public boolean isEmpty() {
return front == rear;
}

// 判断队满
public boolean isFull() {
return (rear + 1) % capacity == front;
}

// 获取元素个数
public int size() {
return (rear - front + capacity) % capacity;
}

// 测试
public static void main(String[] args) {
CircularQueue cq = new CircularQueue(5); // 容量5,最多存4个元素
cq.enqueue(1);
cq.enqueue(2);
cq.enqueue(3);
cq.enqueue(4);
System.out.println("入队4个元素后是否满:" + cq.isFull()); // true

System.out.println("出队:" + cq.dequeue()); // 1
System.out.println("出队后元素个数:" + cq.size()); // 3

cq.enqueue(5);
System.out.println("入队5后元素个数:" + cq.size()); // 4(未超上限)
System.out.println("队头元素:" + cq.peek()); // 2
}
}

输出结果

1
2
3
4
5
入队4个元素后是否满:true
出队:1
出队后元素个数:3
入队5后元素个数:4
队头元素:2

循环队列的优缺点

优点

  • 空间利用率高:解决了普通队列的 “假满” 问题,充分利用数组空间。
  • 操作高效:入队、出队、判断空满的时间复杂度均为 O (1)。

缺点

  • 浪费一个空间:为区分空满,需预留 1 个空间(可通过额外变量记录元素个数解决,但增加复杂度)。
  • 容量固定:基于数组实现,初始化后容量不可动态扩展(需重建数组扩容)。

适用场景

  • 已知最大元素数量的场景(如固定大小的缓冲区、线程池任务队列)。
  • 对效率要求高,需频繁入队出队的场景(如生产者 - 消费者模型)。

总结

循环队列通过逻辑上环化数组,有效解决了普通队列的假满问题,是数组队列的高效优化版本。其核心在于通过 frontrear 指针的循环移动,结合 (rear+1)%capacity == front 判断队满,front == rear 判断队空,实现了 O (1) 时间复杂度的基本操作。尽管浪费一个空间,但在固定容量场景中,其高效性和简洁性使其成为优选方案

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10