1. 什么是队列
队列是数据结构中比较重要的一种类型,它支持FIFO,头部删除(先进先出的元素先出队列),跟我们生活中的排队类似。
2. 队列的种类
·单队列(单队列就是常见的队列,每次添加元素时,都是添加到队尾,存在"假溢出"的问题,也就是明明有位置也不能添加的情况)
以数组实现的队列为例,初始时队列长度固定为 4 ,font 和 rear均为0;
每添加一个元素,rear 后移一位,每当添加四个元素后,rear 到了索引为4的位置。
这时 a1,a2出队,font后移动到2:
这时想要再添加两个元素,但rear后移两位后就会越界;
·循环队列(避免了"假溢出"的问题)
rear = (rear - size) % size
接着上面的例子,当rear大于队列长度时,rear = (5 - 5)% 5 = 0
这样继续添加时,还可以添加几个元素
那如何判断队列是否装满元素了呢,单使用 front == rear 无法判断究竟是空的还是满了。
两种方法:
1. 加个标志 flag,初始为 false,添加满了置为 true
2. 不以 front = rear 为放满标志,改为(rear - front)% size = 1;
方法2的公式放满元素是空余了一个位置,这个公式是什么意思呢?
接着上面的情况,当rear从后面添加元素跑到前面 0 时,再添加一个元素 a6,rear后移一位到1,这时 front = 2,(1-2)% 5 = 1,满足放满条件。
因此,当rear > font 时,队列中元素的个数 = rear - font;
当 rear < font 时,队列中元素分为两部分:size-font 和rear,也就是rear+ size - font. 以上述图片为例,队列中元素个数 = 1- 5 - 2 = 4。
3. Java集合框架中的队列 Queue
Java 集合中的Queue 继承自Collection 接口,Deque, LinkedList, PriorityQueue, BlockingQueue 等类都实现了它,Queue 用来存放 等待处理元素的集合,这种场景一般用于缓冲,并发访问。除了继承Collection接口的一些方法,Queue还添加了额外的 添加 删除 查询操作。
添加 删除 查询这种操作都提供了两种形式,其中一种在操作失败时直接抛出异常,而另外一种则返回一个特殊的值:
Queue 方法介绍:
1. add(E),offer(E) 在尾部添加:
boolean add(E e);
boolean offer(E e);
他们的共同之处是建议实现类禁止添加null元素,否则报空指针 NullPointerException;
不同之处在于 add()方法在添加失败(比如队列已满)时 会报一些运行时错误;而offer()方法即使在添加失败时也不会崩溃,只会返回false。
*注意
Queue 是个接口,它提供的add,offer 方法初衷是以往子类能够禁止添加元素为null,这样可以避免在查询是返回null究竟是正确还是错误。
事实上大多数Queue的实现类的确响应了Queue接口的规定,比如ArrayBlockingQueue,PriorityBlockingQueue等等
但还是有一些实现类没有这样的要求,比如LinkedList。
2. remove(),poll() 删除并返回头部:
E remove();
E poll();
当队列为空时 remove()方法会报NoSuchElementException 错;而poll() 不会崩溃,只返回null。
3. element(),peek() 获取但不删除:
E element();
E peek();
当队列为空时 element()抛出异常;peek()不会奔溃,只会返回null;
其他
1. 虽然LinkedList 没有禁止添加null,但是一般情况下Queue的实现类都不允许添加null元素,为啥呢?
因为 poll(),peek()方法在异常的时候会返回null,你添加了null以后,当获取是不好分辨就是是否正确返回。
2. Queue 一般都是FIFO的,但是也有例外,比如优先队列 priority queue(它的顺序 是根据自然排序或者自定义comparator);再比如LIFO 的队列(跟栈一样,后来进去的先出去)。
不论进入,出去的先后顺序是怎么样的,使用remove(),poll() 方法操作的都是 头部 的元素;而插入的位置则不一定是在队尾了,不同的queue会有不同的插入逻辑。