队列及其实现

来源:互联网 发布:数据库exists 编辑:程序博客网 时间:2024/06/06 11:36

和栈相反,队列(Queue)是一种先进先出(First In First Out,缩写为FIFO)的线性表。它只允许在表的一端进行插入,而在另一端进行删除元素。这和我们日常生活中的排队是一致的,最早进入队列的元素最早离开。

在队列中,允许插入的一端叫做队尾(rear),允许删除的一端则成为队头(front)。向队尾插入元素称为进队或入队,新元素入队后成为新的队尾元素;从队头删除元素称为离队或出队,其后续元素成为新的队首元素。

1.队列的顺序存储与实现

在队列的顺序存储中,我们可以将队列当作一般的数组加以实现,但这样做的效果并不好。尽管我们可以用一个指针rear来指示队尾,使得enqueue运算可以在O(1)时间内完成,但是在执行dequeue时,为了删除队首元素,必须将数组中其他所有元素都向前移动一个位置,这样,当队列中有n个元素时,dequeue就需要O(n)时间。

为此呢,我们也可以考虑增加一个front指针来指向队首的位置,这样的话删除队首元素只需要将front指针后移即可,如下图所示:

这里写图片描述

但是这样又有一个问题,就是当rear指针移动到数组最后的时候,这时候队列并没有满,数组的前面还有空余的空间,所以也不宜进行存储再分配来扩大数组空间。

一个比较巧妙的办法是将顺序队列臆造为一个环状的空间,即数组A[0..capacity-1]中的单元不是排成一排,而是围成一个圆环,即A[0]接在A[capacity-1]后面。如下图所示:

这里写图片描述

上面的数组,我们可以称之为循环数组,而用循环数组实现的队列称为循环队列。我们用front指针指向队首元素所在的单元,用rear指针指向队尾元素所在单元的下一个单元。如上图所示,队首元素存储在数组下标为0的位置,front=0,队尾元素存储在数组下标为2的位置,rear=3。


但是,这里又有一个问题,即如何表示满队列和空队列。

举例来说,如下图(b)所示,在该循环队列中,队首元素为e0,队尾元素为e3。当e4、e5、e6、e7相继入队列后,如图(c)所示,队列空间被占满,此时队尾指针rear追上队首指针front,即有rear=front。反之呢,如果从图(b)所示的状态开始,e0、e1、e2、e3相继出队,则得到空队列,此时队首指针front追上队尾指针rear,所以也有front=rear。

这里写图片描述

可见,仅凭front=rear是无法判断队列状态是“空”还是“满”的。

解决这个问题有两个处理思路,一种即增加一个变量size,用它表示队列中的元素的个数,通过size的大小即可判断队列是否为满或者空。第二种思路就是少用一个存储单元,当队尾指针的下一个单元就是队首指针所指单元时,则队列已满,停止入队。这样队尾指针就不会追上队首指针,而队列满时就不会有front=rear了,而是需要满足(rear+1) % capacity = front。而队列判空的条件不变,仍是front=rear

下述代码基于第二种思路,利用循环数组实现了循环队列:

public class ArrayQueue<T> {    private final int DEFAULT_CAPACITY = 8; // 默认容量    private T[] elements;// 数组    private int front;// 队头指针    private int rear; // 队尾指针    @SuppressWarnings("unchecked")    public ArrayQueue() {        elements = (T[]) new Object[DEFAULT_CAPACITY];        front = 0;        rear = 0;    }    /**     * 队列的大小,即队列中元素的个数     *      * @return     */    public int size() {        // 注意负数,-1 % 8 = -1 而不是 7,所以这里要加上elements.length        return (rear - front + elements.length) % elements.length;    }    /**     * 判空     *      * @return     */    public boolean isEmpty() {        return front == rear;    }    /**     * 判断队列是否满     *      * @return     */    private boolean isFull() {        return (rear + 1) % elements.length == front;    }    /**     * 入队     *      * @param e     */    public void enqueue(T e) {        if (isFull()) {            ensureCapacity();        }        elements[rear] = e;        rear = (rear + 1) % elements.length;    }    /**     * 扩充容量     */    @SuppressWarnings("unchecked")    private void ensureCapacity() {        // 新的数组        T[] temp = (T[]) new Object[elements.length * 2 + 1];        int size = size();        for (int i = 0; i < size; i++) {            temp[i] = elements[front];            front = (front + 1) % elements.length;        }        front = 0;        rear = size;        elements = temp;    }    /**     * 出队     *      * @return     */    public T dequeue() {        if (isEmpty()) {            // 暂时先抛出这个异常            throw new RuntimeException();        }        T e = elements[front];        front = (front + 1) % elements.length;        return e;    }}

2.队列的链式存储与实现

队列的链式存储使用单链表来实现。为了方便,这里使用带头结点的单链表。

根据单链表的特点,我们可以选择链表的头部作为队首,尾部作为队尾。

public class LinkedQueue<T> {    class Node {        private T data;// 数据域        private Node next;// 指针域        public Node() {            this(null, null);        }        public Node(T data, LinkedQueue<T>.Node next) {            super();            this.data = data;            this.next = next;        }        public T getData() {            return data;        }        public void setData(T data) {            this.data = data;        }        public Node getNext() {            return next;        }        public void setNext(Node next) {            this.next = next;        }    }    private Node front; // 队头指针    private Node rear; // 队尾指针    private int size; // 元素个数    public LinkedQueue() {        front = new Node();        rear = front;        size = 0;    }    /**     * 判空     *      * @return     */    public boolean isEmpty() {        return size == 0;    }    /**     * 队列的大小,即元素的个数     *      * @return     */    public int size() {        return size;    }    /**     * 入队     *      * @param e     */    public void enqueue(T e) {        Node newNode = new Node(e, null);        rear.setNext(newNode);        rear = newNode;        size++;    }    public T dequeue() {        if (isEmpty()) {            // 暂时抛出这个异常            throw new RuntimeException();        }        front = front.getNext();        size--;        return front.getData();    }}

代码使用了size这个成员变量来指示队列的大小。这样,所有的操作都可以在O(1)时间内完成。

0 0
原创粉丝点击