队列

队列
循环队列
双端队列

示例练习>>>

队列(queue)是一种先进先出的数据结构(FIFO, First-In-First-Out),就像大家排队买票一样,先来的先买,没有特殊窗口,也没有 VIP 通道,所有人都一样。队列可以使用链表或数组来实现,队列只允许在后端(tail)插入,在前端(head)删除。

队列的实现方式

队列常见有两种实现方式,一种是使用链表,一种是使用数组。使用单向链表实现队列不会出现溢出的问题,队列长度也没有限制,但删除的时间代价较高,所以我们一般使用双向链表,使用两个指针,一个指向队列的头,一个指向队列的尾,删除的时候只能删除头(head)节点,添加的时候只能在尾部(tail)添加,如下图所示。

队列的链表实现

先来看下队列的链表实现,其实他和我们前面讲的双向链表一样,唯一区别就是不能在中间进行添加和删除操作,并且在队列的两头只能一个添加一个删除,先来看下队列的节点类。

class QueueNode<E> {
    E val;// 节点值。
    QueueNode pre;// 指向前一个节点。
    QueueNode next;// 指向后一个节点。

    public QueueNode(E val, QueueNode pre, QueueNode next) {
        this.val = val;
        this.pre = pre;
        this.next = next;
    }
}

来看下使用链表实现队列的完整代码,具体的添加和删除细节可以参考下双向链表,这里就不在重复介绍。

public class Queue<E> {
    private QueueNode<E> head;// 队列的头。
    private QueueNode<E> tail;// 队列的尾。
    private int size;// 队列中元素的个数。

    // 往队列中添加元素。
    public void add(E val) {
        if (isEmpty()) {// 如果队列为空。
            head = new QueueNode(val, null, null);
            tail = head;
        } else {// 如果不为空就添加到末尾。
            tail = new QueueNode<>(val, tail, null);
            tail.pre.next = tail;
        }
        size++;
    }

    // 删除队列的头部元素。
    public E remove() {
        if (isEmpty())// 队列为空,没法删除。
            throw new NullPointerException("队列为空");
        E res = head.val;
        if (size() == 1) {// 只有一个节点,全部删除。
            head = null;
            tail = null;
        } else {// 否则删除头节点。
            QueueNode headNext = head.next;
            head.next = null;
            headNext.pre = null;
            head = headNext;
        }
        size--;
        return res;
    }

    // 获取队列头部元素。
    public E top() {
        if (isEmpty())
            throw new NullPointerException("队列为空");
        return head.val;
    }

    // 队列中元素的个数。
    public int size() {
        return size;
    }

    // 队列是否为空。
    public boolean isEmpty() {
        return size == 0;
    }
}

队列的数组实现

除了使用链表实现队列以外,还可以使用数组来实现。使用链表是没有长度限制的,但使用数组需要给一个固定的大小,如果队列满了还会涉及到队列的扩容。使用两个指针,一个指向队列头 head ,一个指向队列尾 tail ,这里我们让 tail 指向下一个可以存放数据的位置,如下图所示。

来看下代码:

public class Queue2<E> {
    private final Object[] data; // 队列中存放元素的数组
    private final int maxSize;// 队列的最大容量
    private int size; // 队列中元素的个数
    private int head = 0;// 队列的头,只能删除,指向队列的第一个元素
    private int tail = 0; // 队列的尾,只能添加,指向队列最后一个元素的下一个位置

    public Queue2(int maxSize) {
        if (maxSize <= 0)
            throw new IllegalArgumentException("队列容量必须大于0 : " + maxSize);
        this.maxSize = maxSize;
        data = new Object[this.maxSize];
    }

    //这地方可以扩容也可以抛异常,为了方便这里我们就不在扩容了,关于扩容,大家可以自己去实现
    public void add(E val) {
        if (isFull())
            throw new IllegalStateException("队列已经满了,无法再加入……");
        data[tail++] = val;
        size++;
    }

    // 删除
    public E remove() {
        if (isEmpty())
            throw new IllegalStateException("队列是空的,无法删除……");
        E res = (E) data[head];
        data[head++] = null;
        size--;
        return res;
    }

    // 获取队列头部元素
    public E top() {
        if (isEmpty())
            throw new IllegalStateException("队列为空");
        return (E) data[head];
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean isFull() {
        return tail == maxSize;
    }

    public int size() {
        return size;
    }
}

相关链接

数组
数组滚动数组差分数组树状数组
链表
链单向表双向链表循环链表跳表异或链表
队列
队列循环队列双端队列
散列表
散列表
二叉树二叉搜索树AVL树红黑树字典树哈夫曼树线段树笛卡尔树
图的介绍图的遍历Dijkstra算法Bellman-Ford算法SPFA算法Floyd算法Prim算法Kruskal算法Boruvka算法拓扑排序

信奥赛编程(刷题请进)>>>

经过两年的打磨,我的新作《算法秘籍》已经出版,有需要的可以点击购买。也可以点击 内容介绍 查看详情。