队列及其实现

和栈相反,队列(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)时间内完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值