用Java如何实现环形队列
1. 数组模拟队列的问题
- 数组使用一次就不能用,没有达到复用的效果。
2. 思路一
- 初始位置:
front
和rear
都在 -1。front
指向队列第一个元素的前一个位置;rear
指向队列最后一个元素。 - 队列元素个数记录:
size
,用来判断队伍是否满或空。 - 队列为空:当
size
==0 时,队列为空。 - 队列已满:当
size
>=maxSize
时,队列已满。 - 添加数据临界条件:当
rear
==maxSize -1
且队列没满时,将rear
置为 -1,回到初始位置。 - 弹出数据临界条件:当
front
==maxSize -1
且队列没满时,将front
置为 -1,回到初始位置。
思路一的特点就是:队头指针空,队尾指针不空。数组空间全部利用,没有一点浪费。并且我还添加了泛型结构,确保队列中的数据类型都是一致的。
3. 思路一代码实现
① 环形队列:
public class CircularQueue<T> {
//属性
private int front;//队头指针
private int rear;//队尾指针
private int maxSize;//队列最大容量
private int size;//队列数据个数
private Object[] queue;//队列数组
//空参构造器
public CircularQueue() {
front = -1;
rear = -1;
maxSize = 16;
}
//指定容量构造器
public CircularQueue(int maxSize) {
this();
this.maxSize = maxSize;
}
//获取队列数据个数
public int getSize() {
return size;
}
//添加数据
public void add(T t) {
//懒汉式:要添加数据了才创建数组
if (queue == null) {
queue = new Object[maxSize];
}
//判断队列是否已满
if (size >= maxSize) {
throw new RuntimeException("队列已满");
}
//走到这说明队列没满,考虑边界情况
if (rear == maxSize - 1) {
rear = -1;//循环到队伍开头,形成闭环
}
//真正添加数据的操作
queue[++rear] = t;
//数据个数加一
size++;
}
//弹出数据
public T pop() {
//判断队列是否为空
if (queue == null || queue.length == 0 || size == 0) {
throw new RuntimeException("队列为空");
}
//边界情况
if (front == maxSize - 1) {
front = -1;
}
T t = (T) queue[++front];
queue[front] = null;
size--;
return t;
}
//重写toString()
@Override
public String toString() {
return "CircularQueue{" +
"queue=" + Arrays.toString(queue) +
'}';
}
}
② 测试:
@Test
public void test1() {
CircularQueue<Integer> queue = new CircularQueue<>(4);
queue.add(1);
queue.add(2);
queue.add(3);
queue.add(4);
System.out.println(queue.getSize());
System.out.println(queue);
//出队测试
queue.pop();
queue.pop();
System.out.println(queue.getSize());
System.out.println(queue);
//环形添加数据测试
queue.add(5);
queue.add(6);
System.out.println(queue.getSize());
System.out.println(queue);
//环形弹出数据测试
queue.pop();
queue.pop();
queue.pop();
queue.pop();
System.out.println(queue.getSize());
System.out.println(queue);
//再添加数据
queue.add(7);
queue.add(8);
queue.add(9);
queue.add(10);
System.out.println(queue.getSize());
System.out.println(queue);
}
输出:
4
CircularQueue{queue=[1, 2, 3, 4]}
2
CircularQueue{queue=[null, null, 3, 4]}
4
CircularQueue{queue=[5, 6, 3, 4]}
0
CircularQueue{queue=[null, null, null, null]}
4
CircularQueue{queue=[9, 10, 7, 8]}
4. 思路二
队列未满 | 队列已满 | 循环一次队列满的情形 |
---|---|---|
队列初始为空 | 队列将要弹空 |
---|---|
- 初始位置:
front
和rear
的初始值都等于 0 。front
指向队列第一个元素;rear
指向队列最后一个元素的后一个位置。因为希望空出一个位置判断队列空满情况。 - 队列元素个数:不用
size
记录数据个数,而是用公式(rear + maxSize - front) % maxSize
计算得出。 - 队列为空:当
front
==rear
时,队列为空。 - 队列已满:当
(rear+1) % maxSize
==front
时 (图片下面的字错了,以这里为准),队列已满。这一条设计得非常精妙,取模%
是一个哈希操作,能够把运算结果限定在0 ~ maxSize - 1
的区间内。 非常适合限制队尾指针rear
的范围,避免空指针异常。 - 添加数据临界条件:当
rear
==maxSize -1
且队列没满时,将rear
置为 -1,回到初始位置。 - 弹出数据临界条件:当
front
==maxSize -1
且队列没满时,将front
置为 -1,回到初始位置。
思路二的特点就是:队头指针不空,队尾指针为空。数组空间没有全部利用,只能利用 maxSize - 1
个位置。算法精华在于利用取模 %
运算限制指针的范围在数组内,避免空指针异常。
5. 思路二代码实现
① 环形队列:
public class CircularQueueTeacher<T> {
//属性
private int front;//队头指针
private int rear;//队尾指针
private int maxSize;//队列最大容量
private Object[] queue;//队列数组
//空参构造器
public CircularQueueTeacher() {
this.maxSize = 16;
}
//指定容量构造器
public CircularQueueTeacher(int maxSize) {
this();
this.maxSize = maxSize;
}
//获取队列数据个数
public int getSize() {
return (rear + maxSize - front) % maxSize;
}
//添加数据
public void add(T t) {
//懒汉式:要添加数据了才创建数组
if (queue == null) {
queue = new Object[maxSize];
}
//判断队列是否已满
if ((rear + 1) % maxSize == front) {
throw new RuntimeException("队列已满");
}
//真正添加数据的操作
queue[rear] = t;
//边界条件,确保rear在数组范围内,并实现环形移动
rear = (rear + 1) % maxSize;
}
//弹出数据
public T pop() {
//判断队列是否为空
if (rear == front) {
throw new RuntimeException("队列为空");
}
//真正弹出数据的操作
T t = (T) queue[front];
//将弹出位置置为空
queue[front] = null;
//边界条件,确保front在数组范围内,并实现环形移动
front = (front + 1) % maxSize;
//返回弹出数据
return t;
}
//重写toString()方法
@Override
public String toString() {
return "CircularQueueTeacher{" +
"queue=" + Arrays.toString(queue) +
'}';
}
}
② 测试:
@Test
public void testTeacherCode() {
CircularQueueTeacher<Integer> queue = new CircularQueueTeacher<>(4);
//添加数据测试
queue.add(1);
queue.add(2);
queue.add(3);
System.out.println(queue.getSize());
System.out.println(queue);
//出队测试
queue.pop();
queue.pop();
System.out.println(queue.getSize());
System.out.println(queue);
//环形添加数据测试
queue.add(4);
queue.add(5);
System.out.println(queue.getSize());
System.out.println(queue);
//环形弹出数据测试
queue.pop();
System.out.println(queue.getSize());
System.out.println(queue);
//再添加数据
queue.add(6);
System.out.println(queue.getSize());
System.out.println(queue);
}
输出:
3
CircularQueueTeacher{queue=[1, 2, 3, null]}
1
CircularQueueTeacher{queue=[null, null, 3, null]}
3
CircularQueueTeacher{queue=[5, null, 3, 4]}
2
CircularQueueTeacher{queue=[5, null, null, 4]}
3
CircularQueueTeacher{queue=[5, 6, null, 4]}