1.动态队列实现思路
1)实现定长定数据类型的队列。
2)通过泛型的方式实现对多种类型数据元素的支持。
3)根据队内元素数量调整数组长度,实现队列长度的动态变化。
4)实现Iterable和Iterator两个接口提供对for-each语句的支持。
2.定长定数据类型的队列实现
首先考虑定长的字符串队列q,那么需要head和tail两个计数器来记录队列头部和尾部在数组中的位置。
当有元素进队时,将元素值赋给q[tail],tail后移;当有元素出队时,返回q[head]处的值,head后移。
- 保证head <= tail < q.length,当head=tail时,队列为空。
FixedCapacityQueueOfStrings的API如下表所示:
返回类型 | 方法 | 说明 |
---|---|---|
FixedCapacityQueueOfStrings(int cap) | 构造函数 | |
void | enqueue(String s) | 字符串s进队 |
String | dequeue() | 队列头部的元素出队 |
boolean | isEmpty() | 队列是否为空 |
int | size() | 返回队列中元素个数 |
3.泛型
上面的队列只能存储字符串,而通过泛型可以支持任意类型的元素。要实现泛型,只需把上面的队列中的所有String
关键字改为Item
,然后将类的声明语句改为:
pulic class FixedCapacityQueue<Item>
值得注意的一点是,在该类的构造方法中,并不能直接通过常规的创建数组代码创建泛型数组,下面的代码在Java中是不允许的:
a = new Item[cap];//错误,找不到Item类
要创建泛型数组,只能先创建Object数组,然后通过显式转换的方法将数组转化成Item数组,代码如下:
a = (Item[]) new Object[cap];
上面的代码也说明了该队列只能支持引用数据类型的变量,而对于int,double等基本数据类型变量,则需要用到Java的自动装箱、自动拆箱机制,Java会在适当的时候将这些基本数据类型变量打包为其对应的Integer,Double类的对象,反之亦然。
4.动态队列的实现
可以定义一个私有成员方法resize(int max),它会创建一个长度为max的Item数组,然后把当前队列里从head到tail-1的元素复制到该新建数组的0到tail-head-1中,然后将该数组赋给当前队列,同时将head和tail的值更新。代码如下:
private void resize(int max){
Item[] res = (Item[]) new Object[max];
for(int i=head;i<tail;i++)
res[i-head]=queue[i];
queue = res;
tail = N; //N为队列内元素数量,N=tail-head
head = 0;
}
而动态队列的实现方法就是:
每次进队操作时,判断tail是否即将越界,如果即将越界,则比较队列元素数量N与数组长度length的关系:
如果N < length/2,则说明元素集中在数组后半段,无需扩充数组,只需要将元素移到数组前端;
如果N >= length/2, 则将数组长度翻倍,同时将元素移位。
每次出队操作时,判断队列中元素数量N与数组长度length的关系:
- 如果N < length/4,则将数组长度减半,这么做是为了保证数组减半后还有一定的裕度支持几次出入队的操作。
5.Iterable 和 Iterator 接口
foreach语句是java5的新特征之一,在遍历数组、集合方面,foreach为开发人员提供了极大的方便。foreach语句的用法如下:
Queue<String> queue = new Queue<String>();
for(String s:queue){
System.out.println(s);
}
该语句与下面的while语句等价:
Iterator<String> i = queue.Iterator();
while(i.hasNext()){
String s = i.next();
System.out.println(s);
}
以上代码说明了在任意可迭代集合类型中都要实现的东西:
实现一个iterator()并返回一个Iterator对象。
返回的Iterator对象必须包含两个方法:hasNext()和next()。
以上这两点分别可以通过实现Iterable接口和Iterator接口来完成。在队列类的声明中增加对Iterable接口的实现,代码如下:
public class Queue<Item> implements Iterable<Item>
然后在类中添加方法iterator()并返回一个Iterator迭代器,需要注意的是迭代器都是泛型的,因此可以在队列类中添加以下方法:
public Iterator<Item> iterator() {
return new QueueIterator();
}
其中返回的new QueueIterator是实现了Iterator接口的类实例,Iterator接口下有hasNext(),next(),remove()三个方法,均需要覆盖。QueueIterator类只用于队列遍历,因此将它实现在队列类的一个嵌套类中:
private class QueueIterator implements Iterator<Item>{
private int i = head;
@Override
public boolean hasNext() {
return i!=tail;
}
@Override
public Item next() {
return queue[i++];
}
public void remove() {}
}
其中,嵌套类可以访问包含它的类的实例变量,而且Iterator接口的三个方法都必须声明为public。最后,还需要从java.util中导入Iterator接口。将这两个接口实现后就可以通过foreach语句对队列元素进行遍历。
6.完整代码
import java.util.Iterator;
import java.util.Scanner;
public class Queue<Item> implements Iterable<Item> {
private Item[] queue;
private int N,head,tail;//N代表当前队列内元素数量
public Queue(){
queue = (Item[])new Object[1];//此句编译器会有警告,不过不影响运行
N = 0;
head = 0;
tail = 0;
}
public void enqueue(Item item){ //进队操作
if(tail==queue.length){
if(N<=queue.length/2)
resize(queue.length);
else resize(queue.length*2);
}
queue[tail++]=item;
N++;
}
public Item dequeue(){ //出队操作
Item out = null;
if(!isEmpty()){
out = queue[head];
queue[head++]=null; //将出队处的元素设为空,避免对象游离,占用内存资源。
N--;
if(N<queue.length/4)
resize(queue.length/2);
return out;
}
return out; //如果队列为空则返回null
}
private void resize(int max){
Item[] res = (Item[]) new Object[max];
for(int i=head;i<tail;i++)
res[i-head]=queue[i];
queue = res;
tail = N;
head = 0;
}
public boolean isEmpty(){
return head==tail;
}
public int head(){
return head;
}
public int tail(){
return tail;
}
public int getCapacity(){
return queue.length;
}
@Override
public Iterator<Item> iterator() {
return new QueueIterator();
}
private class QueueIterator implements Iterator<Item>{
private int i = head;
@Override
public boolean hasNext() {
return i!=tail;
}
@Override
public Item next() {
return queue[i++];
}
public void remove() {}
}
public static void main(String[] args) {
Queue<String> collection = new Queue<String>();
Scanner in = new Scanner(System.in);
while(in.hasNext()){
String s1 = in.next();
if(!s1.equals("-")) { //如果输入为“-”则将该输入加入队列尾部
collection.enqueue(s1);
System.out.println("----enqueue----");
}
else { //输入不为“-”,则队头元素出队并打印到控制台
System.out.println(collection.dequeue());
}
System.out.println("head: "+collection.head()+" tail: "+collection.tail()); //输出每次操作后head与tail的值,观察其变化规律
}
for(String s : collection){
System.out.println("Iterator: "+s);
}
}
}
控制台输入:
to be or not to - be - - that - - - is
控制台输出:
----enqueue----
head: 0 tail: 1
----enqueue----
head: 0 tail: 2
----enqueue----
head: 0 tail: 3
----enqueue----
head: 0 tail: 4
----enqueue----
head: 0 tail: 5
to //队列头部元素出队并打印
head: 1 tail: 5
----enqueue----
head: 1 tail: 6
be
head: 2 tail: 6
or
head: 3 tail: 6
----enqueue----
head: 3 tail: 7
not
head: 4 tail: 7
to
head: 5 tail: 7
be
head: 0 tail: 1 //动态调整数组长度,同时更新head,tail位置
----enqueue----
head: 0 tail: 2 //最后还剩两个元素
Iterator: that //foreach语句遍历剩下的两个元素
Iterator: is