队列是对类似排队现象的抽象,一头只能进数据,另一头只能出数据,遵守“先进先出”的规则。底层可以有顺序存储和链式存储两种实现方式,本文以顺序存储为例讲解并通过Java编程实现入队、出队的基本功能,考虑不周的地方欢迎讨论交流。
顺序存储即按数组存储,特点是开辟的数组空间是定死的,一般不采取走一个元素就把所有元素往前挪一步的做法,而是让它们各自还在自己的位置上。队尾不断有元素加进来,队头有元素出队而产生空缺,这就造成一个问题,那就是可能会出现队尾到了数组末尾(队满),而队头实际上有空缺的现象(实际没满)。循环队列就是为了解决这个问题,我们可以想象一个车轮子,队列在上面一圈一圈地排,只要首尾不相接就永远没有“假溢出”的现象。但本质上还是个一维数组,只是头尾指针到了数组末尾会自动回到开头,好像一个环一样。
头指针指向最先进来的元素,当要出队时就“释放”头指针所指元素,该指针指向后一个元素。这里并不是真的释放,数据实际上还在那个位置,只不过通过指针的移动放弃了它,下次新的数据会把它覆盖掉。尾指针指向即将入队的位置,即最后一个入队元素的下一个位置。
判断队列是否为空很简单,只要看看首尾指针是否指向同一个地址即可。判断队列是否为满就有讲究了,毕竟空和满的首尾指针相对位置差不多。一种解决思路是空出一个存储单元不存储数据,专门用来防止首尾指针因队满再次相遇。这样,队满时尾指针应该指向这个空位置,而头指针在它的下一个位置,从而很好地和队空进行了区分。
这里我们以4个存储空间的数组为例,演示循环队列实现过程。依次进行添加1、2、3,删除1、2、3,添加4、5,删除4的操作,观察整个存储空间和首尾指针的位置变化情况。
利用代码模拟,运行结果如下图所示。
可以看到,结果还是一致且准确的。附上核心代码。出入队列时加上了队空队满的判断,满时入队、空时出队都会抛出异常。
public class Queue {
private static final int MAXSIZE = 4; //最大空间
private int[] queue; //队列
private int frontPointer, rearPointer; //头尾指针
public Queue() {
queue = new int[MAXSIZE];
frontPointer = 0;
rearPointer = 0;
}
//判断为空
public boolean isEmpty() {
return frontPointer == rearPointer;
}
//判断为满
public boolean isFull() {
return frontPointer == (rearPointer + 1) % MAXSIZE;
}
//入队
public void enQueue(int element) {
if (isFull()) throw new RuntimeException("队列为满!");
queue[rearPointer] = element;
rearPointer = (rearPointer + 1) % MAXSIZE;
}
//出队
public void deQueue() {
if(isEmpty()) throw new RuntimeException("队列为空!");
frontPointer = (frontPointer + 1) % MAXSIZE;
}
//打印队列
public void show() {
//显示有效队列
System.out.print("当前排队情况:");
int i = frontPointer;
while(i % MAXSIZE != rearPointer) {
System.out.print(queue[i % MAXSIZE]);
i+=1;
}
System.out.print('\n');
//显示队列存储全部情况
System.out.print("队列总体情况:");
for(int j = 0; j < 4; j++) {
System.out.print(queue[j]);
}
System.out.println(" front=" + frontPointer + " rear=" + rearPointer + '\n');
}
}