Java数据结构:队列

1. 概述

队列是“先进先出”(FIFO,First InFirst Out)的数据结构。队列的概念就好比乘火车时候买票的队伍,先到的人当然可以优先买票,买完后就从前端离去准备乘火车,而队尾的后端又陆续有新的乘客加入。

它有下列特性:

a. 先进先出(FIFO)。

b. 队列只允许在"队首"进行删除操作,而在"队尾"进行插入操作。 队列通常包括的两种操作:入队列 和 出队列。

队列的示意图:(图片来自百度图片)

队列分为:

①单向队列:只能在一堆插入数据,另一端删除数据。

②双向队列:每一端都可以进行插入数据和删除数据操作。 

2. 队列的实现

以下例子使用数组实现单向队列,能存储int类型的数据。

package zzw.cn.queue;

/**
 * @author 鹭岛猥琐男
 * @create 2019/8/13 22:59
 */
public class ArrayQueue
{
    private int[] mArray;
    private int mCount;

    /* 构造方法 */
    public ArrayQueue(int size)
    {
        mArray = new int[size];
        mCount = 0;
    }

    // 将value添加到队列的末尾
    public void add(int value)
    {
        mArray[mCount++] = value;
    }

    // 返回队首元素值,并删除队首元素
    public int pop()
    {
        int ret = mArray[0];
        mCount--;
        for (int i = 1; i <= mCount; i++)
            mArray[i - 1] = mArray[i];
        return ret;
    }

    // 返回队列的大小
    public int size()
    {
        return mCount;
    }

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

}

测试队列:

package zzw.cn.queue;

/**
 * @author 鹭岛猥琐男
 * @create 2019/8/13 22:57
 */
public class TestQueue
{
    public static void main(String[] args)
    {
        ArrayQueue queue = new ArrayQueue(10);
        //往队列中添加元素
        for (int i = 0; i < 10; i++)
        {
            queue.add(i);
        }
        System.out.println("队列添加元素后,队列的长度为:" + queue.size());
        //从队首中逐一取出元素
        System.out.println("逐一取出元素:");
        while (!queue.isEmpty())
        {
            System.out.print(queue.pop() + " ");
        }
        //再次往队列中添加元素
        for (int i = 0; i < 10; i++)
        {
            queue.add(i + 10);
        }
        System.out.println("\n队列添加元素后,队列的长度为:" + queue.size());
    }
}

测试结果:

队列添加元素后,队列的长度为:10
逐一取出元素:
0 1 2 3 4 5 6 7 8 9 
队列添加元素后,队列的长度为:10

 从以上结果,此队列是属于先进先出的。

3. 双向队列

在上面中,模拟了单向队列的实现。对于双向队列,在JDK中已经有相应的API实现: ArrayDeque。ArrayDeque 是 Deque 接口的一种具体实现,是依赖于可变数组来实现的。ArrayDeque 没有容量限制,可根据需求自动进行扩容。ArrayDeque不支持值为 null 的元素。

Deque 接口继承自 Queue接口,Queue 也是 Java 集合框架中定义的一种接口,直接继承自 Collection 接口。Queue 是按照先进先出(FIFO)的方式来管理其中的元素的。

Queue的源码:


public interface Queue<E> extends Collection<E> {

    boolean add(E e);

    boolean offer(E e);

    E remove();

    E poll();

    E element();

    E peek();
}

从以上代码可以看出,Queue是一个接口,并且继承自 Collection 接口。

继续看 Deque 的源码:

package java.util;

public interface Deque<E> extends Queue<E> {

    void addFirst(E e);

    void addLast(E e);

    boolean offerFirst(E e);

    boolean offerLast(E e);

    E removeFirst();

    E removeLast();

    E pollFirst();

    E pollLast();

    E getFirst();

    E getLast();

    E peekFirst();

    E peekLast();

    boolean removeFirstOccurrence(Object o);

    boolean removeLastOccurrence(Object o);

    boolean add(E e);

    boolean offer(E e);

    E remove();

    E poll();

    E element();

    E peek();

    void push(E e);

    E pop();

    boolean remove(Object o);

    boolean contains(Object o);

    public int size();

    Iterator<E> iterator();

    Iterator<E> descendingIterator();

}

从以上代码可以看出,Deque也是一个接口,并且继承自 Queue接口。

继续看 ArrayDeque的源码:

public class ArrayDeque<E> extends AbstractCollection<E>
                           implements Deque<E>, Cloneable, Serializable
{
    ...
    ...
}

 从此段伪代码可以看出,ArrayDeque 实现了接口 Deque。

ArrayDeque 的构造方法:

    /**
     * Constructs an empty array deque with an initial capacity
     * sufficient to hold 16 elements.
     */
    public ArrayDeque() {
        elements = new Object[16];
    }

    /**
     * Constructs an empty array deque with an initial capacity
     * sufficient to hold the specified number of elements.
     *
     * @param numElements  lower bound on initial capacity of the deque
     */
    public ArrayDeque(int numElements) {
        allocateElements(numElements);
    }

    /**
     * Constructs a deque containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.  (The first element returned by the collection's
     * iterator becomes the first element, or <i>front</i> of the
     * deque.)
     *
     * @param c the collection whose elements are to be placed into the deque
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayDeque(Collection<? extends E> c) {
        allocateElements(c.size());
        addAll(c);
    }

这里只分析下第一个构造方法,在此方法中,创建了大小为16的 Object 类型的数组。

底层结构:

    /**
     * The array in which the elements of the deque are stored.
     * The capacity of the deque is the length of this array, which is
     * always a power of two. The array is never allowed to become
     * full, except transiently within an addX method where it is
     * resized (see doubleCapacity) immediately upon becoming full,
     * thus avoiding head and tail wrapping around to equal each
     * other.  We also guarantee that all array cells not holding
     * deque elements are always null.
     */
    transient Object[] elements; // non-private to simplify nested class access

    /**
     * The index of the element at the head of the deque (which is the
     * element that would be removed by remove() or pop()); or an
     * arbitrary number equal to tail if the deque is empty.
     */
    transient int head;

    /**
     * The index at which the next element would be added to the tail
     * of the deque (via addLast(E), add(E), or push(E)).
     */
    transient int tail;

    /**
     * The minimum capacity that we'll use for a newly created deque.
     * Must be a power of 2.
     */
    private static final int MIN_INITIAL_CAPACITY = 8;

 ArrayDeque 底部是使用数组存储元素,同时还使用了两个索引来表征当前数组的状态,分别是 head 和 tail。head 是头部元素的索引,但注意 tail 不是尾部元素的索引,而是尾部元素的下一位,即下一个将要被加入的元素的索引。

添加元素的方法:

 这三个方法的源码如下:

    public boolean add(E e) {
        addLast(e);
        return true;
    }

    public void addFirst(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[head = (head - 1) & (elements.length - 1)] = e;
        if (head == tail)
            doubleCapacity();
    }

    public void addLast(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[tail] = e;
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)
            doubleCapacity();
    }

 在以上方法中,add 方法最终调用到 addFirst 方法,支持从队列的头部和尾部添加元素。这里分析下向末尾添加元素的方法 addLast:

这段代码中,当传入的 E e为空时候,则抛出 NullPointerException;如果不为空时,则赋值给 elements[tail] = e。这也证实了 ArrayDeque 是不允许值为空的。当 tail 和 head 的值相等时候,则调用 doubleCapacity() 方法。这里为何要判断是否相等呢?这是因为在 ArrayDeque 中数组是当作环形来使用的,索引0看作是紧挨着索引(length-1)之后的。

    private void doubleCapacity() {
        assert head == tail;
        int p = head;
        int n = elements.length;
        int r = n - p; // number of elements to the right of p
        int newCapacity = n << 1;
        if (newCapacity < 0)
            throw new IllegalStateException("Sorry, deque too big");
        Object[] a = new Object[newCapacity];
        System.arraycopy(elements, p, a, 0, r);
        System.arraycopy(elements, 0, a, r, p);
        elements = a;
        head = 0;
        tail = n;
    }

这里的扩容是进行翻倍的扩容: int newCapacity = n << 1。这里还是跟 ArrayList 有所区别的。

测试:

package zzw.cn.queue;

import java.util.ArrayDeque;

/**
 * @author 鹭岛猥琐男
 * @create 2019/8/19 20:21
 */
public class TestDeque
{
    public static void main(String[] args)
    {
        ArrayDeque<Integer> deque = new ArrayDeque<>();
        //向尾部添加元素
        deque.addLast(20);
        deque.addLast(21);
        deque.addLast(22);
        deque.addLast(23);
        //向头部添加元素
        deque.addFirst(1);
        deque.addFirst(2);
        deque.addFirst(3);
        //按照这样的添加顺序,这里的顺序应该为:3,2,1,20,21,22,23
        System.out.println("队列元素为:");
        for (Object o : deque)
        {
            System.out.print(o + ",");
        }
    }
}

测试结果:

队列元素为:
3,2,1,20,21,22,23,

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值