队列(Queue)另外一种被限制的线性表,它使用固定的一端来插入数据,另一端只用于删除元素。也就是说,队列中的元素的移动方向总是固定的,就像排队购物一样:先进入队伍的顾客先获得服务,队伍中的顾客总是按固定方向移动,只有当排在自己前面的所有顾客获得服务后,当前顾客才能获得服务。
队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,只允许在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除的端称为队头。如果队列中不包含任何元素,该队列就被称为空队列。
对于一个队列来说,每个元素总是从队列的rear端进入队列,然后等待该元素之前的所有元素出队列后,当前元素才出对。因此,把队列简称为先进先出(FIFO)的线性表。对列示意图如下:
对列的常用操作:
对列同样作为线性表,通常不该提供线性表的如下方法:
(1)获取指定索引处的元素。
(2)按值查找数据元素的索引位置。
(3)在指定索引处插入新元素。
(4)删除指定索引处的元素。
从上面的这些方法可以看出,队列不应该提供从中间任意位置访问元素的方法。也就是说,队列只允许在队列的前端(front)删除元素和后端(rear)添加元素。
队列通常有如下一些方法:
(1)初始化:通常是一个构造器,用于创建一个空队列。
(2)返回队列的长度:该方法用于返回队列中数据元素的个数。
(3) 加入元素:向队列的后端(rear)添加新元素,队列的长度 +1。
(4) 删除元素:从队列的前端(front)删除元素,队列的长度 -1,并放回该元素。
(5) 访问队列前端元素:返回队列的前端(front)元素,但不删除该元素。
(6)判断队列是否为空:该方法判断队列是否为空,如果为空则返回true;否则返回false。
(7)清空队列元素:该方法清空队列的所有元素。
对于队列这种数据结构,上面的加粗字体就是他的标志性方法。类似于线性表的实现可以用顺序存储结构也可以用链式存储结构。队列也可以用顺序存储结构和链式存储结构这两种接过来实现。
系统采用一组连续的存储单元来依次保存队列的rear端到front端的所有数据元素,程序只需front和rear两个整形变量来记录队列的front端的元素索引和rear端的元素索引。顺序存储结构的队列简称为顺序队列。如下图所示顺序队列示意图:
从图中可以看出,顺序队列的front总是保存着队列中即将出列的数据元素的索引;顺序队列中的rear总是保存着下一个即将进入队列的元素的索引。队列中的元素的个数为rear-front。
对于上面的顺序队列的程序实现,数据元素在底层数组中的位置是固定的,改变的只是rear、front两个整形变量的值。这个队列可能出现所谓的“假满”现象。如下图所示:
从图中可以看出,此时rear等于该队列底层的数组容量capacity,如果此时试图向队列中添加元素,将会引起“队列已满”异常。其实这是一种“假满”现象。
此时该队列底层的数组依然有6个空位可以存储数据元素,但程序已经加不进去了。
对于假满问题,程序有如下解决方法:
(1)每次讲元素移除对了时都将队列中的所有元素向front端移动一位,这种方式下front值永远为 0 ,有元素每次插入队列时rear值+1,有元素移除时队列rear值-1.但这种方式非常浪费时间,
因为每次将元素从队列中删除时都需要进行“整体搬家”。
(2)将数组存储区看成是一个收尾相连的环形区域,当存放到数组的最大地址之后,rear的值再次变为 0 。采用这种技巧存储的队列称为循环队列。
队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,只允许在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除的端称为队头。如果队列中不包含任何元素,该队列就被称为空队列。
对于一个队列来说,每个元素总是从队列的rear端进入队列,然后等待该元素之前的所有元素出队列后,当前元素才出对。因此,把队列简称为先进先出(FIFO)的线性表。对列示意图如下:
对列的常用操作:
对列同样作为线性表,通常不该提供线性表的如下方法:
(1)获取指定索引处的元素。
(2)按值查找数据元素的索引位置。
(3)在指定索引处插入新元素。
(4)删除指定索引处的元素。
从上面的这些方法可以看出,队列不应该提供从中间任意位置访问元素的方法。也就是说,队列只允许在队列的前端(front)删除元素和后端(rear)添加元素。
队列通常有如下一些方法:
(1)初始化:通常是一个构造器,用于创建一个空队列。
(2)返回队列的长度:该方法用于返回队列中数据元素的个数。
(3) 加入元素:向队列的后端(rear)添加新元素,队列的长度 +1。
(4) 删除元素:从队列的前端(front)删除元素,队列的长度 -1,并放回该元素。
(5) 访问队列前端元素:返回队列的前端(front)元素,但不删除该元素。
(6)判断队列是否为空:该方法判断队列是否为空,如果为空则返回true;否则返回false。
(7)清空队列元素:该方法清空队列的所有元素。
对于队列这种数据结构,上面的加粗字体就是他的标志性方法。类似于线性表的实现可以用顺序存储结构也可以用链式存储结构。队列也可以用顺序存储结构和链式存储结构这两种接过来实现。
系统采用一组连续的存储单元来依次保存队列的rear端到front端的所有数据元素,程序只需front和rear两个整形变量来记录队列的front端的元素索引和rear端的元素索引。顺序存储结构的队列简称为顺序队列。如下图所示顺序队列示意图:
从图中可以看出,顺序队列的front总是保存着队列中即将出列的数据元素的索引;顺序队列中的rear总是保存着下一个即将进入队列的元素的索引。队列中的元素的个数为rear-front。
下面是代码实现:
import java.util.Arrays;
public class SequenceQueue <T> {
//默认队列容量为10
private final int DEFAULT_CAPACITY = 10;
//底层数组
private Object[] elementData;
//容量
private int capacity;
//
private int front = 0;
private int rear = 0;
public SequenceQueue(){
this.capacity = DEFAULT_CAPACITY;
elementData = new Object[capacity];
}
public SequenceQueue(T element){
this();
elementData[0] = element;
rear ++;
}
public SequenceQueue(T element, int initCapacity){
this.capacity = initCapacity;
elementData = new Object[capacity];
elementData[0] = element;
rear ++;
}
//队列长度
public int length(){
return rear - front;
}
//判是否为空
public boolean isEmpty(){
return rear == front;
}
//向队列rear端插入元素
public void add(T element){
indexOutOfBoundsForAdd(rear);
elementData[rear ++] = element;
}
//向队列front端删除并返回 该元素
@SuppressWarnings("unchecked")
public T remove(){
noneElementForRemove();
T t = (T) elementData[front];
elementData[front ++] = null;
return t;
}
//element()返回但不删除front端元素
@SuppressWarnings("unchecked")
public T element(){
noneElementForRemove();
return (T) elementData[front];
}
//清空队列
public void clear(){
Arrays.fill(elementData, null);
front = 0;
rear = 0;
}
//toString 方法
public String toString(){
if(isEmpty()){
return "[]";
}
else{
StringBuilder sb = new StringBuilder("[");
for(int i = front; i < rear; i ++){
sb.append(elementData[i].toString() + ",");
}
return sb.toString().substring(0, sb.length() - 1) + "]";
}
}
private void indexOutOfBoundsForAdd(int index){
if(index > capacity - 1){
throw new IndexOutOfBoundsException("exception for SequenceQueue is filled:index of add is " + index);
}
}
private void noneElementForRemove(){
if(isEmpty()){
throw new IndexOutOfBoundsException("none any element in SequenceQueue!");
}
}
}
测试代码如下:
import com.yc.list.SequenceQueue;
public class SequenceQueueTest {
public static void main(String[] args) {
SequenceQueue<String> queue = new SequenceQueue<String>("aaa", 4);
queue.add("bbb");
queue.add("ccc");
queue.add("ddd");
System.out.println( "队列为: " + queue);
System.out.println();
String sElement = queue.element();
System.out.println( "队首为: " + sElement);
System.out.println( "队列为: " + queue);
System.out.println();
String sRemove = queue.remove();
System.out.println( "队首为: " + sRemove);
System.out.println( "队列为: " + queue);
System.out.println();
queue.add("eee");
}
}
运行结果为:
从上面的运行结果来看,当程序执行 String sRemove = queue.remove();这句后,队首的值 aaa 从队列移除,此时队列有3个元素,讲道理程序再执行add.("eee");是可行的,
但出现了上面的异常,这也是这个实现的代码的不足之处:出现“假满”现象。
对于上面的顺序队列的程序实现,数据元素在底层数组中的位置是固定的,改变的只是rear、front两个整形变量的值。这个队列可能出现所谓的“假满”现象。如下图所示:
从图中可以看出,此时rear等于该队列底层的数组容量capacity,如果此时试图向队列中添加元素,将会引起“队列已满”异常。其实这是一种“假满”现象。
此时该队列底层的数组依然有6个空位可以存储数据元素,但程序已经加不进去了。
对于假满问题,程序有如下解决方法:
(1)每次讲元素移除对了时都将队列中的所有元素向front端移动一位,这种方式下front值永远为 0 ,有元素每次插入队列时rear值+1,有元素移除时队列rear值-1.但这种方式非常浪费时间,
因为每次将元素从队列中删除时都需要进行“整体搬家”。
(2)将数组存储区看成是一个收尾相连的环形区域,当存放到数组的最大地址之后,rear的值再次变为 0 。采用这种技巧存储的队列称为循环队列。