一、引言

栈是一种后进先出(First in First out FIFO)的线性数据结构,队列从队尾添加元素,从队首取出元素。

二、实现

  • 队列接口
/**
 * 队列接口
 * @param <E>
 */
public interface Queue<E> {

    /**
     * 获取队列中元素个数
     * @return 队列中元素个数
     */
    int getSize();

    /**
     * 判断是否空队列
     * @return
     */
    boolean isEmpty();

    /**
     * 入队操作
     * @param e 入队元素
     */
    void enqueue(E e);

    /**
     * 出队操作
     * @return 出队元素
     */
    E dequeue();

    /**
     * 查询队首元素
     * @return 队首元素
     */
    E getFront();

}

1、基于动态数组实现队列

  • 动态数组类

/**
 * 动态数组,数组二次封装
 */
public class Array<E> {

    /**
     * 基于Java原生数组,保存数据的容器
     */
    private E[] data;

    /**
     * 当前元素个数
     */
    private int size;

    public Array(int capacity) {
        data = (E[]) new Object[capacity];
        size = 0;
    }

    /**
     * 默认数组容量capacity=10
     */
    public Array() {
        this(10);
    }

    /**
     * 获取数组中元素个数
     * @return
     */
    public int getSize() {
        return size;
    }

    /**
     * 获取数组的容量
     * @return
     */
    public int getCapacity() {
        return data.length;
    }

    /**
     * 判断数组是否为空
     * @return
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 在所有元素后面添加新元素
     * @param e 元素
     */
    public void addLast(E e) {
        add(size, e);
    }

    /**
     * 在所有元素前面添加新元素
     * @param e 元素
     */
    public void addFirst(E e) {
        add(0, e);
    }

    /**
     * 向index索引位置插入一个新元素e
     * @param index 数组索引位置
     * @param e 元素
     */
    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("addList failed. index < 0 || index > size");
        }

        //空间不足,扩容
        if (size == data.length) {
            resize(2 * data.length);
        }

        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }
        data[index] = e;
        size++;
    }

    /**
     * 根据元素索引获取数组元素
     * @param index 索引
     * @return
     */
    public E get(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("get failed. index is illegal");
        }
        return data[index];
    }

    /**
     * 获取数组最后一个元素
     * @return
     */
    public E getLast() {
        return get(size - 1);
    }

    /**
     * 获取数组第一个元素
     * @return
     */
    public E getFirst() {
        return get(0);
    }

    /**
     * 根据元素索引修改数组元素
     * @param index 索引
     * @param e 元素
     * @return
     */
    public void set(int index, E e) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("get failed. index is illegal");
        }
        data[index] = e;
    }

    /**
     * 判断包含元素
     * @param e 元素
     * @return
     */
    public boolean contains(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 查找元素索引
     * @param e 元素
     * @return 返回元素索引,如果不存在则返回-1
     */
    public int find(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 移除指定索引的元素
     * @param index 索引
     * @return 返回被移除的元素
     */
    public E remove(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("get failed. index is illegal");
        }
        E ret = data[index];
        for (int i = index + 1; i < size; i++) {
            data[i - 1] = data[i];
        }
        size--;
        data[size] = null;

        //空间利用率低,数组缩容,防止复杂度震荡
        if (size == data.length / 4 && data.length / 2 != 0) {
            resize(data.length / 2);
        }

        return ret;
    }

    /**
     * 移除第一个元素
     * @return 返回被移除元素
     */
    public E removeFirst() {
        return remove(0);
    }

    /**
     * 移除最后一个元素
     * @return 返回被移除元素
     */
    public E removeLast() {
        return remove(size - 1);
    }

    /**
     * 移除数组中一个元素
     * @param e 元素
     */
    public void removeElement(E e) {
        int index = find(e);
        if (index != -1) {
            remove(index);
        }
    }

    /**
     * 数组容器扩容、缩容
     * @param newCapacity 新的容量
     */
    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("Array: size = %d, capacity = %d\n", size, data.length));
        res.append("[");
        for (int i = 0; i < size; i++) {
            res.append(data[i]);
            if (i != size - 1) {
                res.append(", ");
            }
        }
        res.append("]");
        return res.toString();
    }

}
  • 队列实现
/**
 * 队列的数组实现方式
 * @param <E>
 */
public class ArrayQueue<E> implements  Queue<E> {

    /**
     * 动态数组
     */
    private Array<E> array;

    /**
     * 初始化队列容量
     * @param capacity
     */
    public ArrayQueue(int capacity) {
        array = new Array<>(capacity);
    }

    /**
     * 默认初始化队列
     */
    public ArrayQueue() {
        array = new Array<>();
    }

    @Override
    public int getSize() {
        return array.getSize();
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }

    /**
     * 获取队列容量
     * @return 队列容量
     */
    public int getCapacity() {
        return array.getCapacity();
    }

    @Override
    public void enqueue(E e) {
        array.addLast(e);
    }

    @Override
    public E dequeue() {
        return array.removeFirst();
    }

    @Override
    public E getFront() {
        return array.getFirst();
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("Queue: ");
        res.append("front [");
        for (int i = 0; i < array.getSize(); i++) {
            res.append(array.get(i));
            if (i != array.getSize() - 1) {
                res.append(", ");
            }
        }
        res.append("] tail");
        return res.toString();
    }

}

2、循环队列

  • 队列实现
/**
 * 循环队列
 * @param <E>
 */
public class LoopQueue<E> implements Queue<E> {

    /**
     * 数组容器
     */
    private E[] data;

    /**
     * 队首游标
     */
    private int front;

    /**
     * 队尾游标
     */
    private int tail;

    /**
     * 队列中元素个数
     */
    private int size;

    /**
     * 初始化队列
     * @param capacity
     */
    public LoopQueue(int capacity) {
        //队列浪费一个空间,为了好判断队列是否为空
        data = (E[]) new Object[capacity + 1];
        front = 0;
        tail = 0;
        size = 0;
    }

    /**
     * 默认初始化队列
     */
    public LoopQueue() {
        this(10);
    }

    public int getCapacity() {
        return data.length - 1;
    }

    /**
     * 获取队列中元素个数
     * @return
     */
    @Override
    public int getSize() {
        return size;
    }

    /**
     * 判断队列是否为空,队列满元素时,队首尾游标不重合,默认浪费一个数组空间
     * @return
     */
    @Override
    public boolean isEmpty() {
        return front == tail;
    }

    /**
     * 入队操作
     * @param e 入队元素
     */
    @Override
    public void enqueue(E e) {
        //队列满元素(浪费一个空间)
        if ((tail + 1) % data.length == front) {
            resize(getCapacity() * 2);
        }
        data[tail] = e;
        tail = (tail + 1) % data.length;
        size ++;
    }

    @Override
    public E dequeue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("cannot dequeue from an empty queue");
        }
        E ret = data[front];
        data[front] = null;
        front = (front + 1) % data.length;
        size--;

        //缩容
        if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
            resize(getCapacity() / 2);
        }
        return ret;
    }

    @Override
    public E getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("cannot dequeue from an empty queue");
        }
        return data[front];
    }

    /**
     * 队列扩缩容
     * @param newCapacity 容量
     */
    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity + 1];
        for (int i = 0; i < size; i++) {
            newData[i] = data[(i + front) % data.length];
        }
        data = newData;
        front = 0;
        tail = size;
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity()));
        res.append("front [");
        for (int i = front; i != tail; i = (i + 1) % data.length) {
            res.append(data[i]);
            if ((i + 1) % data.length != tail) {
                res.append(", ");
            }
        }
        res.append("] tail");
        return res.toString();
    }

    public static void main(String[] args) {
        LoopQueue<Integer> queue = new LoopQueue<>();
        for (int i = 0; i < 10; i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}

3、基于链表实现队列

  • 链表队列实现
/**
 * 基于链表实现队列
 * @param <E>
 */
public class LinkedListQueue<E> implements Queue<E> {

    /**
     * 节点
     */
    private class Node {
        public E e;
        public Node next;

        public Node(E e,Node next) {
            this.e = e;
            this.next = next;
        }

        public Node(E e) {
            this(e, null);
        }

        public Node() {
            this(null, null);
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }

    /**
     * 链表头节点
     */
    private Node head;

    /**
     * 链表尾节点
     */
    private Node tail;

    /**
     * 链表中元素个数
     */
    private int size;

    public LinkedListQueue() {
        head = null;
        tail = null;
        size = 0;
    }

    /**
     * 获取队列中元素个数
     * @return
     */
    @Override
    public int getSize() {
        return size;
    }

    /**
     * 判断队列中是否有元素
     * @return
     */
    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 入队操作
     * @param e 入队元素
     */
    @Override
    public void enqueue(E e) {
        if (tail == null) {
            tail = new Node(e);
            head = tail;
        } else {
            tail.next = new Node(e);
            tail = tail.next;
        }
        size++;
    }

    /**
     * 出队操作
     */
    @Override
    public E dequeue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("cannot dequeue from an empty queue");
        }

        Node removeNode = head;
        head = head.next;
        removeNode.next = null;
        if (head == null) {
            tail = null;
        }
        size--;
        return removeNode.e;
    }

    /**
     * 查询队首元素
     * @return
     */
    @Override
    public E getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("query is empty");
        }
        return head.e;
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("queue: front ");

        Node cur = head;
        while (cur != null) {
            res.append(cur + "->");
            cur = cur.next;
        }
        res.append("NULL tail");
        return res.toString();
    }

    public static void main(String[] args) {
        LinkedListQueue<Integer> queue = new LinkedListQueue<>();
        for (int i = 0; i < 10; i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}

3、基于最大堆实现优先队列

  • 最大堆实现(依赖动态数组)
/**
 * 基于动态数组实现最大堆
 * @param <E>
 */
public class MaxHeap<E extends Comparable<E>> {

    private Array<E> data;

    public MaxHeap(int capacity) {
        data = new Array<>(capacity);
    }

    public MaxHeap() {
        data = new Array<>();
    }

    /**
     * 普通数组堆化
     * @param arr
     */
    public MaxHeap(E[] arr) {
        data = new Array<>(arr);
        //从第一个非叶子节点(叶子节点无需下沉操作)开始遍历,并且执行下沉操作,完成堆化
        for (int i = parent(arr.length - 1); i >= 0; i--) {
            siftDown(i);
        }
    }

    /**
     * 获取堆中元素个数
     * @return
     */
    public int size() {
        return data.getSize();
    }

    /**
     * 判断堆中是否为空
     * @return
     */
    public boolean isEmpty() {
        return data.isEmpty();
    }

    /**
     * 返回二叉堆的数组表示中,一个索引所表示的元素的父亲节点的索引
     * @param index 节点在数组中的索引
     * @return
     */
    private int parent(int index) {
        if (index == 0) {
            throw new IllegalArgumentException("index-0 doesn't have parent");
        }

        return (index - 1) / 2;
    }

    /**
     * 返回二叉堆的数组表示中,一个索引所表示的元素的左孩子节点的索引
     * @param index 节点在数组中的索引
     * @return
     */
    private int leftChild(int index) {
        return index * 2 + 1;
    }

    /**
     * 返回二叉堆的数组表示中,一个索引所表示的元素的右孩子节点的索引
     * @param index 节点在数组中的索引
     * @return
     */
    private int rightChild(int index) {
        return index * 2 + 2;
    }

    /**
     * 向堆中添加元素
     * @param e 待添加元素
     */
    public void add(E e) {
        data.addLast(e);
        siftUp(data.getSize() - 1);
    }

    /**
     * 堆中元素上浮
     * @param k 元素索引
     */
    private void siftUp(int k) {
        //当前节点的元素比父亲节点的元素大则上浮
        while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
            //交换数组中的元素
            data.swap(k, parent(k));
            k = parent(k);
        }
    }

    /**
     * 查询堆中最大元素
     * @return
     */
    public E findMax() {
        if (data.getSize() == 0) {
            throw new IllegalArgumentException("can not finMax when heap is empty");
        }
        return data.get(0);
    }

    /**
     * 取出堆中最大元素
     * @return
     */
    public E extractMax() {
        E max = findMax();

        //交换堆中最大的元素与堆尾元素
        data.swap(0, data.getSize() - 1);

        //删除堆尾元素
        data.removeLast();

        //元素下沉
        siftDown(0);

        return max;
    }

    /**
     * 堆中元素下沉
     * @param k 元素索引
     */
    private void siftDown(int k) {

        //只要该元素的左孩子索引没有越界,继续处理
        while (leftChild(k) < data.getSize()) {
            int j = leftChild(k);
            if (j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0) {
                j = rightChild(k);
            }

            //data[j] 是leftChild 和 rightChild中的最大值
            if (data.get(k).compareTo(data.get(j)) >= 0) {
                break;
            }

            //下沉
            data.swap(k, j);
            k = j;
        }

    }

    /**
     * 取出堆中的最大元素,并且替换成元素e
     * @param e 待替换元素
     * @return 堆中最大元素
     */
    public E replace(E e) {

        E max = findMax();

        //覆盖最大元素
        data.set(0, e);

        //被元素可能破坏了堆的结构,触发下沉操作
        siftDown(0);

        return max;
    }

}
  • 优先队列实现
/**
 * 优先队列基于最大堆实现
 * @param <E>
 */
public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {

    /**
     * 最大堆容器
     */
    private MaxHeap<E> maxHeap;

    public PriorityQueue() {
        maxHeap = new MaxHeap<>();
    }

    /**
     * 获取队列中元素个数
     * @return
     */
    @Override
    public int getSize() {
        return maxHeap.size();
    }

    /**
     * 判断队列是否为空
     * @return
     */
    @Override
    public boolean isEmpty() {
        return maxHeap.isEmpty();
    }

    /**
     * 入队操作
     * @param e 入队元素
     */
    @Override
    public void enqueue(E e) {
        maxHeap.add(e);
    }

    /**
     * 出队操作
     * @return
     */
    @Override
    public E dequeue() {
        return maxHeap.extractMax();
    }

    /**
     * 查询队首元素
     * @return
     */
    @Override
    public E getFront() {
        return maxHeap.findMax();
    }
}

三、性能测试

  • 测试队列入队、出队耗时
import java.util.Random;

public class Main {

    public static void main(String[] args) {
        int opCount = 100000;

        ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
        double arrayQueueTime = testQueue(arrayQueue, opCount);
        System.out.println("ArrayQueue, time:" + arrayQueueTime + "s");//ArrayQueue, time:3.0009159s

        LoopQueue<Integer> loopQueue = new LoopQueue<>();
        double loopQueueTime = testQueue(loopQueue, opCount);
        System.out.println("LoopQueue, time:" + loopQueueTime + "s");//LoopQueue, time:0.0104942s

        LinkedListQueue<Integer> linkedListQueue = new LinkedListQueue<>();
        double linkedListQueueTime = testQueue(linkedListQueue, opCount);
        System.out.println("LinkedListQueue, time:" + linkedListQueueTime + "s");//LinkedListQueue, time:0.008738501s
    }

    /**
     * 测试使用队列运行入队、出队操作所需的时间
     * @param queue 待测试队列
     * @param opCount 操作次数
     * @return
     */
    private static double testQueue(Queue<Integer> queue, int opCount) {
        long statTime = System.nanoTime();

        Random random = new Random();
        for (int i = 0; i < opCount; i++) {
            queue.enqueue(random.nextInt(Integer.MAX_VALUE));
        }

        for (int i = 0; i < opCount; i++) {
            queue.dequeue();
        }

        long endTime = System.nanoTime();

        return (endTime - statTime) / 1000000000.0;
    }
}

说明: 以上测试是在性能一般的个人笔记本上完成的,基于JDK1.8。分别进行了10万次入队出队操作,性能相差近300倍。这点得益于循环队列的实现是通过队首、队尾游标标识首尾元素的,循环队列在出队操作时不需要移动任何元素,只需要维护好游标。循环队列相比于基于动态数组队列的出队操作,时间复杂度从O(n)变成了O(1)(均摊);基于链表实现的队列由于有头节点和尾节点的存在,时间复杂度也是O(1)的。

四、复杂度分析

1、基于数组实现的复杂度分析

  • enqueue(e) => O(1) : 入队操作是先动态数组末尾添加元素,时间复杂度与数组中元素个数无关,入队操作可能触发数组扩容,但是触发概率较小,均摊复杂度接近O(1)。
  • dequeue() => O(n) : 出队操作移出的是数组第一个元素,其后所有元素都需要向前移动,时间复杂度跟元素个数正相关
  • getFront() => O(1) : 查询队首元素实际上是查询的是数组第一个元素,可通过数组索引迅速定位,不会造成元素移动。
  • getSize() => O(1) : 实际上是数组的游标值,无需重新计算。
  • isEmpty() => O(1) : 类似于getSize.

2、循环队列复杂度分析

  • dequeue() => O(1) : 出队操作移出的是数组第一个元素,无需移动其后的元素,只需维护队首游标,虽然可能触发缩容操作(O(n)),此处的O(1)为均摊复杂度

3、优先队列复杂度

  • enqueue(e) => O(logn) : 入队操作是二叉堆元素下沉的一个过程,最多下沉操作次数是二叉堆这个完全二叉树的最大高度。
  • dequeue() => O(logn) : 出队操作是二叉堆元素上浮的一个过程,最多上浮操作次数是二叉堆这个完全二叉树的最大高度。

五、其它数据结构


neojayway
52 声望10 粉丝

学无止境,每天进步一点点