Java数据结构之Queue(队列)

Queue(队列)

Queue(队列)是一种特殊的线性表,它只允许在队头(front)进行删除操作(出队),在队尾(rear)进行插入操作(入队),是一种操作受限制的线性表。即:FIFO(First In, First Out)。
在这里插入图片描述
从Queue的继承关系图可以看到,Queue接口实现了Collection接口,所以Queue具有Collection接口的所有特性。

但是一般来说,在操作Queue时,一般只操作Queue的相关特性方法即可。否则何必使用Queue呢?

Queue常见的方法

// 入队
boolean offer(E e);
// 出队
E poll();
// 获取队头元素(不出队)
E peek();
// 判断队列是否为空
boolean isEmpty();
// 清空队列
void clear();
// 获取队列的元素个数
int size();

自己实现一个Queue(链表实现)

public class MyLinkedQueue<E> {

    private List<E> queueList = new LinkedList<>();

    // 入队
    public boolean offer(E e){
        return queueList.add(e);
    }
    // 出队
    public E poll(){
        checkElementIndex();
        return queueList.remove(0);
    }
    // 获取队头元素(不出队)
    public E peek(){
        checkElementIndex();
        return queueList.get(0);
    }

    private void checkElementIndex(){
        if(isEmpty()){
            throw new IndexOutOfBoundsException("queue is empty.");
        }
    }

    // 判断队列是否为空
    public boolean isEmpty(){
        return queueList.isEmpty();
    }
    // 清空队列
    public void clear(){
        queueList.clear();
    }
    // 获取队列的元素个数
    public int size(){
        return queueList.size();
    }

}

自己实现一个Queue(数组实现)

public class MyArrayQueue<E>{

    private Object[] objects;

    private int size = 0;

    private int front = 0;

    private static final int DEFAULT_CAPACITY = 10;

    public MyArrayQueue(int capacity){
        if(capacity <= 0){
            throw new IllegalArgumentException("capacity must be greater than zero.");
        }
        objects = new Object[capacity];
    }

    public MyArrayQueue(){
        this(DEFAULT_CAPACITY);
    }

    // 入队
    public boolean offer(E e){
        ensureCapacity();
        //因为数组从0开始,所以(front + size) % objects.length就是下一个要插入的位置,而不是+1
        int index = (front + size) % objects.length;
        objects[index] = e;
        size++;
        return true;
    }

    // 确保容量足够插入元素
    private void ensureCapacity() {
        if(size == objects.length){
            // 2倍扩容
            Object[] newObjects = new Object[this.objects.length * 2];
            for (int i = 0; i < size; i++) {
                newObjects[i] = objects[front];
                moveFrontToNext();
            }
            objects = newObjects;
            // 扩容后,队头从开始
            front = 0;
        }
    }

    // 出队
    public E poll(){
        checkElementIndex();
        E e = (E) objects[front];
        moveFrontToNext();
        size--;
        return e;
    }
    // 获取队头元素(不出队)
    public E peek(){
        checkElementIndex();
        return (E) objects[front];
    }

    private void checkElementIndex(){
        if(isEmpty()){
            throw new IndexOutOfBoundsException("queue is empty.");
        }
    }

    // 判断队列是否为空
    public boolean isEmpty(){
        return size == 0;
    }
    // 清空队列
    public void clear(){
        for (int i = 0; i < size; i++) {
            objects[front] = null;
            moveFrontToNext();
        }
        size = 0;
    }
    // 获取队列的元素个数
    public int size(){
        return size;
    }
	// 将front移动到下一个位置。这个是数组实现Queue的核心算法。
    private void moveFrontToNext(){
        front = (front + 1) % objects.length;
    }
}

数组实现Queue为什么底层不使用ArrayList,而是自己实现底层逻辑呢?因为ArrayList对于出队操作,时间复杂度为O(n),性能不友好。而上面的代码通过循环数组的方式,通过front指定队头的位置,就能实现O(1)时间复杂度的出队操作。

数组实现Queue的核心算法就是:front = (front + count) % objects.length。它通过取模底层数组长度,可以定位到入队的位置,从而实现循环数组。如:

​ 假设数组长度为10,队列的front为9(数组下标从0开始,所以front为9时是第10个元素位置),队列长度为5,那么其底层数据的位置分别为:

0 1 2 3 4 5 6 7 8 9
2 3 4 5 1

现在要插入值为6的元素,则6应该是插入到数组下标为4的位置。那么其计算公式为:

front = (front + size) % objects.length = (9 + 5) % 10 = 4

0 1 2 3 4 5 6 7 8 9
2 3 4 5 6 1

Deque

Deque是Queue的子接口。Queue是一种单向的队列形式,Deque则是双向队列,它支持从两个端点方向检索和插入元素。Deque既包含队列性质,也包含了栈的性质,因此Deque既可以支持FIFO形式也可以支持LIFO形式。

Deque常见的方法

// 向队列头部插入一个元素,失败时抛出异常
void push(E e);
// 向队列头部插入一个元素,失败时抛出异常
void addFirst(E e);
// 向队列尾部插入一个元素,失败时抛出异常
void addLast(E e);
// 向队列头部加入一个元素,失败时返回false
boolean offerFirst(E e);
// 向队列尾部加入一个元素,失败时返回false
boolean offerLast(E e);

// 获取队列头部元素,队列为空时抛出异常
E getFirst();
// 获取队列尾部元素,队列为空时抛出异常
E getLast();
// 获取队列头部元素,队列为空时返回null
E peekFirst();
// 获取队列尾部元素,队列为空时返回null
E peekLast();
// 删除第一次出现的指定元素,不存在时返回false
boolean removeFirstOccurrence(Object o);
// 删除最后一次出现的指定元素,不存在时返回false
boolean removeLastOccurrence(Object o);
// 弹出队列头部元素,队列为空时抛出异常
E pop();
// 弹出队列头部元素,队列为空时抛出异常
E removeFirst();
// 弹出队列头部元素,队列为空时返回null
E pollFirst();
// 弹出队列尾部元素,队列为空时返回null
E pollLast();

从上面的方法可以看出,Deque在Queue的方法上新添了对队列头尾元素的操作。

需要注意push = addFirst,pop = removeFirst,只是使用了不同的方法名体现队列表示栈结构时的特点。

Deque的实现

同Queue一样,Deque的实现也可以划分成通用实现和并发实现。

通用实现主要有两个实现类ArrayDeque和LinkedList:

ArrayDeque是个可变数组,它是在Java 6之后新添加的,而LinkedList是一种链表结构的list。LinkedList要比ArrayDeque更加灵活,因为它也实现了List接口的所有操作,并且可以插入null元素,这在ArrayDeque中是不允许的。

从效率来看,ArrayDeque要比LinkedList在两端增删元素上更为高效,因为没有在节点创建删除上的开销。最适合使用LinkedList的情况是迭代队列时删除当前迭代的元素。

总体ArrayDeque要比LinkedList更优越,在大队列的测试上有3倍与LinkedList的性能,最好的是给ArrayDeque一个较大的初始化大小,以避免底层数组扩容时数据拷贝的开销。

LinkedBlockingDeque是Deque的并发实现,在队列为空的时候,它的takeFirst,takeLast会阻塞等待队列处于可用状态。

参考:https://www.cnblogs.com/bushi/p/6681543.html

发布了6 篇原创文章 · 获赞 0 · 访问量 61
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览