1.队列的介绍
队列(queue )简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许
在表的一端进行插入,而在表的另一端进行删除。在队列中把插入数据元素的一端称为 队尾
rear,删除数据元素的一端称为 队首(front) )。向队尾插入元素称为 进队或入队,新元素
入队后成为新的队尾元素;从队列中删除元素称为 离队或出队,元素出队后,其后续元素成
为新的队首元素。
由于队列的插入和删除操作分别在队尾和队首进行,每个元素必然按照进入的次序离
队,也就是说先进队的元素必然先离队,所以称队列为 先进先出表(First In First Out,简称
FIFO)。队列结构与日常生活中排队等候服务的模型是一致的,最早进入队列的人,最早得
到服务并从队首离开;最后到来的人只能排在队列的最后,最后得到服务并最后离开。
1.1队列的抽象定义
public interface Queue { /** *返回队列大小 */ public int getSize(); /** * 判断队列是否为空 */ public boolean isEmpty(); /** *数据元素e进入队列 */ public void enqueue(Object e); /** * 队首出队元素 */ public Object dequeue() throws QueueEmptyException; /** *取队首元素 */ public Object peek() throws QueueEmptyException; }
1.2队列的顺序存储实现
在队列的顺序存储实现中,我们可以将队列当作一般的表用数组加以实现,但这样做的
效果并不好。尽管我们可以用一个指针 last 来指示队尾,使得 enqueue 运算可在Ο(1)时间内
完成,但是在执行 dequeue 时,为了删除队首元素,必须将数组中其他所有元素都向前移动
一个位置。这样,当队列中有 n 个元素时,执行 dequeue 就需要Ο(n)时间。
为了提高运算的效率,我们用另一种方法来表达数组中各单元的位置关系。设想数组
A[0.. capacity-1]中的单元不是排成一行,而是围成一个圆环,即 A[0]接在 A[capacity-1]的后
面。这种意义下的数组称为循环数组,如图 4-3 所示。
用循环数组实现的队列称为循环队列,我们将循环队列中从队首到队尾的元素按逆时针
方向存放在循环数组中一段连续的单元中。并且直接用队首指针 front 指向队首元素所在的
单元,用队尾指针 rear 指向队尾元素所在单元的后一个单元。如图 4-3 所示,队首元素存储
在数组下标为 0 的位置,front=0;队尾元素存储在数组下标为 2 的位置,rear=3。
当需要将新元素入队时,可在队尾指针指示的单元中存入新元素,并将队尾指针 rear 按
逆时针方向移一位。出队操作也很简单,只要将队首指针 front 依逆时针方向移一位即可。
容易看出,用循环数组来实现队列可以在Ο(1)时间内完成 enqueue 和 dequeue 运算。执行一
系列的入队与出队运算,将使整个队列在循环数组中按逆时针方向移动。
当然队首和队尾指针也可以有不同的指向,例如也可以用队首指针 front 指向队首元素
所在单元的前一个单元,或者用队尾指针 rear 指向队尾元素所在单元的方法来表示队列在
循环数组中的位置。但是不论使用哪一种方法来指示队首与队尾元素,我们都要解决一个细
节问题,即如何表示满队列和空队列。
下面以图 4-3 所示的表示方法来说明这个问题。在图 4-3 中用队首指针front指向队首元
素所在的单元,用队尾指针rear指向队尾元素所在单元的后一个单元。如此在图 4-4(b)中
所示循环队列中,队首元素为e 0 ,队尾元素为e 3 。当e 4 、e 5 、e 6 、e 7 相继进入队列后,如图 4-4
(c)所示,队列空间被占满,此时队尾指针追上队首指针,有rear = front。反之,如果从图
4-4(b)所示的状态开始,e 0 、e 1 、e 2 、e 3 相继出队,则得到空队列,如图 4-4(a)所示,此
时队首指针追上队尾指针,所以也有front = rear。可见仅凭front与rear是否相等无法判断队
java数组实现:
public class QueueArray implements Queue { private static final int CAP = 7; //队列默认大小 private Object[] elements; //数据元素数组 private int capacity; //数组的大小,elements.length private int front; //队首指针,指向队首 private int rear; //队尾元素,只想队尾后一个位置 public QueueArray() { this(CAP); } public QueueArray(int cap) { capacity = cap + 1; elements = new Object[capacity]; front = rear = 0; } @Override public int getSize() { return (rear - front + capacity) % capacity; } @Override public boolean isEmpty() { return rear == front; } @Override public void enqueue(Object e) { if (getSize() == capacity - 1) { expandSpace(); } else { elements[rear] = e; rear = (rear + 1) % capacity; } } private void expandSpace() { Object[] a = new Object[elements.length * 2]; int i = front; int j = 0; while (i != rear) { a[j++] = elements[i]; i = (i + 1) % capacity; } elements = a; capacity = elements.length; front = 0; rear = j; //设置新的队首、队尾指针 } @Override public Object dequeue() throws QueueEmptyException { if (isEmpty()) { throw new QueueEmptyException("错误:队列为空"); } Object obj = elements[front]; elements[front] = null; front = (front + 1) % capacity; return obj; } @Override public Object peek() throws QueueEmptyException { if (isEmpty()) { throw new QueueEmptyException("错误:队列为空"); } Object obj = elements[front]; return obj; } }
1.3队列的链式存储实现
队列的链式存储可以使用单链表来实现。为了操作实现方便,这里采用带头结点的单链
表结构。根据单链表的特点,选择链表的头部作为队首,链表的尾部作为队尾。除了链表头
结点需要通过一个引用来指向之外,还需要一个对链表尾结点的引用,以方便队列的入队操
作的实现。为此一共设置两个指针,一个队首指针和一个队尾指针,如图 4-5 所示。队首指
针指向队首元素的前一个结点,即始终指向链表空的头结点,队尾指针指向队列当前队尾元
素所在的结点。当队列为空时,队首指针与队尾指针均指向空的头结点。
java链表实现:
public class QueueSLinked implements Queue { private SLNode front; private SLNode rear; private int size; public QueueSLinked() { this.front = new SLNode(); this.rear = front; this.size = 0; } @Override public int getSize() { return size; } @Override public boolean isEmpty() { return size==0; } @Override public void enqueue(Object e) { SLNode p=new SLNode(e,null); rear.setNext(p); rear=p; size++; } @Override public Object dequeue() throws QueueEmptyException { if (size<1) throw new QueueEmptyException("错误:队列为空"); SLNode p = front.getNext(); front.setNext(p.getNext()); size--; if (size<1) rear=front;//如果队列为空, return p.getData(); } @Override public Object peek() throws QueueEmptyException { if (size<1) throw new QueueEmptyException("错误:队列为空"); return front.getNext().getData(); } }