定义
队列的用处很广,最基本的有进程调度时的先来先服务、页面调度用到的先进先出,这里不讨论这种“FIFO”方式的优缺点,只说队列。队列属于数组概念的一个变种,在处理元素时包含两个属性,头标志和尾标志,对于入队和出队的操作通过移动头、尾来完成。与数组的比较如下
很明显,数组的插入与删除元素操作都在一端进行,而队列的插入元素在尾部进行,删除操作在头部进行。这就存在一个很明显的缺点:
相对于数组的重复利用数组空间而言,队列的空间利用率太低,虽然提供了先进先出的概念作为补偿,但是空间不能重复利用带来的损失太大。
由此诞生了循环队列:
队列的头、尾标志不变,只是在到达申请的数组空间的端点后,从数组开始处继续进行元素的插入、删除。逻辑上可以理解为在一个数组空间上,进行循环操作。如图
如果一来,可以在同一个空间内进行无数次的元素入队、出队操作。
结构
设数组空间大小为:length,头下标:front,尾下标:rear,初始状态:front=rear,即当前下标位置元素都为空
入队操作:
rear=(rear+1)%length;
正常数组中执行:rear++ 即可,此处因为可能到达尾部后从头开始,所以加操作后对数组长度取余数。当下标在数组长度内,则跟正常数组无异,当下标到达数组长度(即越界,因为正常数组最后一个下标是length-1),则从零开始,因此加上个取余操作。
出队操作:
由入队操作可知,出队操作类似:front=(front+1)%length;
根据入队和出队的操作可以看出,尾部rear始终指向一个空的位置,即下一个待入队元素的存储位置。front指向下一个待出队的元素位置(空队时除外)。由此可得出队列为空的条件:当front指向的位置(即待出队的元素位置)为rear指向的位置(即待入队元素的位置),此时队列为空(没有待出队元素)。
此时关于队列为满的判断,则可能需要牺牲掉数组空间的一个位置,因为按照正常“满”的概念,此时rear指向的下一个元素的插入位置,应该等于front指向的位置,即下一个准备入队的元素的位置空间上已经存储了元素,则表示队列“满”了,然而如果这样的话,则队列为满与队列为空的判断条件相同。所以为了避免该混乱情形,在判断队列满时,判断(rear+1)%length 的值是否与front相等,相等则表示队列满,即rear当前指向的空位置不存储元素,整个队列浪费一个存储空间,如果队列长度为1,即length=1时,则队列既为空,也为满,不能入队也不能出队。
(其实除了牺牲rear指向的元素存储空间来表示队列满外,还可以在队列中添加一个表示当前元素个数的属性,以此来判断队列为空为满的情形)
参考代码
class queue{ //循环队列
private int[] arr=null;//数组空间
private int front=0;//头下标
private int rear=0;//尾下标
public queue(int length){
arr=new int[length];
}
public queue(){
arr=new int[10];//默认长度为10
}
public void push(int s){//入队
if(full()){
extend();//队列满则进行扩展
}
arr[rear]=s;
rear=(rear+1)%arr.length;
}
public int pop(){//出队
if(!empty()){
int tem=arr[front];
front=(front+1)%arr.length;
return tem;
}
return -1;
}
public boolean full(){//判断满
return front==(rear+1)%arr.length;
}
public boolean empty(){//判断空
return front==rear;
}
public boolean In(int index){
int i=front,j=rear;
while(i!=j){
if(arr[i]==index){
return true;//i在队列中
}
i=(i+1)%arr.length;
}
return false;//i不在队列中
}
private void extend(){
int index=0;
int new_len=arr.length*2;
int[] new_arr=new int[new_len];
while(front!=rear){
new_arr[index++]=arr[front];
front=(front+1)%arr.length;
}
front=0;
rear=index;
arr=new_arr;
}
}
总结
循环队列的使用较为广泛,主要优点有两个:一是相较于普通数组,提供了先进先出的概念,在使用功能上进行了拓展,二是在普通队列的元素操作方式上进行了改进,增加了空间的使用效率,虽然仍然浪费了一个存储单位的空间,但是相比之前已经优化了太多。