什么是队列?
队列的两个基本操作是inserting(插入)一个数据项,即把一个数据项放入队尾,另一个是removing(移除)一个数据项,即移除队头的数据项。这类似于电影爱好者排队买票时先排到队尾,然后到达队头买票后离开队列。
栈中的插入和移除数据项方法的命名是很标准,称为push和pop。队列的方法至今没有标准化的命名。“插入”可以称为put、add或enque,而“删除”可以叫delete、get或deque。插入数据项的队尾,也可以叫作back、tail或end。而移除数据项的队头,也可以叫head。下面将使用insert、remove、front和rear。
插入将值插入队尾,同时队尾箭头增加一,指向新的数据项。
数据项被移除后,同时队头指针增加一。通常实现队列时,删除的数据项还会保存在内存中,只是它不能被访问了,因为队头指针已经移到它的下一个位置了。
和栈中的情况不同,队列中的数据项不总是从数组的0下标处开始。移除了一些数据项后,队头指针会指向一个较高的下标位置。
查看操作返回队头数据项的值,然而并不从队中删除这个数据项。
要是想从空队列中移除一个数据项或想在已经满的队列中插入一个数据项,应用程序都要提示出错消息。
上方左边为普通队列,右图为环绕式队列
循环队列
当在队列中插入一个新数据项,队头的Rear箭头向上移动,移向数组下标大的位置。移除数据项时,队尾Front指针也会向上移动。这种设计可能和人们直观察觉相反,因为人们在买电影票排队时,队伍总是向前移动的,当前面的人买完票离开队伍后,其他人都向前移动。计算机中在队列里删除一个数据项后,也可以将其他数据项都向前移动,但这样做的效率很差。相反,我们通过队列中队头和队尾指针的移动保持所有数据项的位置不变。
这样设计的问题是队尾指针很快就会移到数组的末端。虽然在数组的开始部分有空的数据项单元,这是移除的数据项的位置,但是由于因为队尾指针不能再向后移动了,因此也不能再插入新的数据项,这该怎么办?
环绕式处理
为了避免队列不满却不能插入新数据项的问题,可以让队头队尾指针绕回到数组开始的位置。这就是循环队列(有时也称为“缓冲环”)。
指针回绕的过程:在队列中插入足够多的数据项,使队尾指针指向数组的未端。再删除几个数组前端的数据项。现在插入一个新的数据项。就会看到队尾指针从未端回绕到开始处的位置。新的数据项将插入这个位置。
插入更多的数据项。队尾指针如预计的那样向上移动。注意在队尾指针回绕之后, 它现在处在队头指针的下面,这就颠倒了初始的位置。这可以称为“折断的序列”:队列中的数据项存在数组两个不同的序列中。
删除足够多的数据项后,队头指针也回绕。这时队列的指针回到了初始运行时的位置状态,队头指针在队尾指针的下面。数据项也恢复为单一的连续的序列。
下面我们实现一个循环处理队列
public class Queue {
private int capacity;
private int[] intArray;
private int head;
private int tail;
private int length;
private static final int HEAD = 0;
private static final int TAIL = -1;
public Queue(int capacity){
this.capacity = capacity;
this.intArray = new int[capacity];
this.head = TAIL;
this.tail = TAIL;
this.length = HEAD;
}
public void add(int intValue) throws Exception {
//判断对是否是满的
if(isFull()){
throw new Exception("队列满了,别操作了");
}
if(tail == capacity -1){
tail = TAIL;
}
intArray[++tail] = intValue;
length++;
}
public int remove() throws Exception {
//判断队列是否是空的
if(isEmpty()){
throw new Exception("队列是空的,别移除了");
}
int intElem = intArray[++head];
if(head == capacity){
head = 0;
}
//由于类型原因,把头赋值成了0,实际赋值成null
intArray[head] = 0;
length --;
return intElem;
}
//队列长度
public int getLength(){
return length;
}
//是否为空
public Boolean isEmpty(){
return length == 0 ? true : false;
}
//是否满了
public Boolean isFull(){
return length == capacity ? true : false;
}
//查看对头元素
public int catHead() throws Exception {
if(isEmpty()){
throw new Exception("队列是空的,你告诉我咋看");
}
return intArray[head];
}
public static void main(String[] args) throws Exception{
Queue queue = new Queue(10);
queue.add(1);
queue.add(2);
queue.add(3);
queue.add(4);
queue.remove();
int i = queue.catHead();
System.out.println(i);
}
}
总结
1、该队列为非线程安全的,在多线程环境中可能会发生数据丢失等问题。
2、队列通过移动指针来确定数组下标的位置,因为是基于数组实现的,所以队列的长度不能够超过数组的长度。
3、该队列是循环队列,这就意味着数组可以重复被使用,避免了重复创建对象带来的性能的开销。
4、添加数据时总是从队列尾部添加,拉取数据时总是从队列头部拉取,拉取完将对象元素删除。