queue

队列可以让人们有效地在尾部添加一个元素, 在头部删除一个元素。有两个端头的队列, 即双端队列,可以让人们有效地在头部和尾部同时添加或删除元素。不支持在队列中间添加元素。在 Java SE 6中引人了 Deque 接口,并由 ArrayDeque 和LinkedList 类实现。这两个类都提供了双端队列,而且在必要时可以增加队列的长度。

 

1 队列:Queue接口

Queue接口设计了一些可以在队列头部删除元素、在尾部添加元素的方法。这个接口比较简单,一共只有6个方法,分别如下:

(1)添加元素

boolean add(E e); boolean offer(E e);

//这两个方法都在尾部添加一个值为e的元素。如果队列没有满,将给定的元素添加到这个队列的尾部并返回true。不同的是如果队列满了,add方法会抛出一个IllegalStateException异常,而offer方法返回false。

(2)删除并返回元素

E remove(); E poll();

这两个方法删除队列头部的元素。如果队列非空,这两个方法删除头部元素后并返回这个元素。不同的是如果队列是空的,remove方法抛出一个NoSuchElementException异常,而poll方法返回false。

(3) 获得头部元素

E element(); E peek();

这两个方法都会返回头部的元素,而都不删除头部元素。不同的是如果队列是空的,element方法抛出一个NoSuchElementException异常,而peek方法返回null。

 

2 双端队列:Deque接口及ArrayDeque类

一般的队列只能在头部删除元素、在尾部添加元素,即只有一个端。而双端队列有两个端,支持在两端同时添加或删除元素。Deque接口是Java SE 6引入的,并由ArrayDeque类和LinkedList类实现,这两个类都提供了双端队列,并在必要的时候可以增加队列的长度。其实,还有一种队列,叫做有限队列,当然也包括有限双端队列,这两个队列不在本文的讨论之内。

 

2.1 Deque接口

Deque接口在Queue接口的基础之上增加了一些针对双端添加和删除元素的方法,这些方法根据出错时的行为也可以分为几组。这些方法就是在Queue接口中的方法名后面加上“First”和“Last”表明在哪端操作。这些方法整理如下:

 

(1)添加元素

void addFirst(E e); void addLast(E e); boolean offerFirst(E e); boolean offerLast(E e);

这四个方法在头部或尾部添加给定的元素。如果队列满了,前两个方法将抛出一个IllegalStateException异常,后两个方法返回false。

(2)删除并返回元素

transient Object[] elements; transient int head; transient int tail;

E removeFirst(); E removeLast(); E pollFirst(); E pollLast();

这四个方法删除头部或尾部的元素并返回。如果队列为空,前两个方法将抛出一个NoSuchElementException异常,后两个方法返回null。

(3)返回但不删除元素

E getFirst(); E getLast(); E peekFirst(); E peekLast();

这四个方法返回头部或尾部的元素,但不删除。如果队列为空,前两个方法将抛出一个NoSuchElementException异常,后两个方法返回null。

 

2.2 ArrayDeque类

队列可以让人们有效地在尾部添加一个元素, 在头部删除一个元素。有两个端头的队列, 即双端队列,可以让人们有效地在头部和尾部同时添加或删除元素。不支持在队列中间添加元素。

 

就其实现而言,ArrayDeque采用了循环数组的方式来完成双端队列的功能。

1. 无限的扩展,自动扩展队列大小的。(当然在不会内存溢出的情况下。)

2. 非线程安全的,不支持并发访问和修改。

3. 支持fast-fail.

4. 作为栈使用的话比比栈要快.

5. 当队列使用比linklist要快。

6. null元素被禁止使用。

 

方法:

S.N.

方法 & 描述

1

boolean add(E e) 

此方法将添加指定的元素,在此deque队列的末尾。

2

void addFirst(E e) 

此方法将添加指定的元素,在此deque队列的前面。

3

void addLast(E e) 

此方法将插入指定的元素,在此deque队列的末尾。

4

void clear() 

此方法移除此deque队列的元素。

5

ArrayDeque clone() 

此方法返回此deque队列的副本。

6

boolean contains(Object o) 

如果此deque 队列包含指定的元素,此方法返回true。

7

Iterator descendingIterator() 

此方法返回一个迭代器在此deque队列以逆向顺序的元素。

8

E element() 

此方法检索,但是不移除此deque队列表示的队列的头部。

9

E getFirst()

此方法检索,但是不移除此deque队列的第一个元素。

10

E getLast() 

此方法检索,但是不移除此deque队列的最后一个元素。

11

boolean isEmpty() 

如果此deque队列不包含元素,此方法返回true。

12

Iterator iterator() 

此方法返回一个迭代器在此deque队列的元素。

13

boolean offer(E e)

此方法将指定的元素,在此deque队列的末尾。

14

boolean offerFirst(E e) 

此方法将指定的元素,在此deque队列的前面。

15

boolean offerLast(E e) 

此方法将指定的元素,在此deque队列的末尾。

16

E peek() 

此方法检索,但是不移除此deque队列表示的队列的头部,如果此deque队列为空,则返回null。

17

E peekFirst() 

此方法检索,但是不移除此deque 队列的第一个元素,或者如果此deque 队列为空,则返回null。

18

E peekLast() 

此方法检索,但是不移除此deque队列的最后一个元素,如果此deque队列为空,则返回null。

19

E poll() 

此方法检索并移除此deque队列表示的队列的头部,如果此deque队列为空,则返回null。

20

E pollFirst() 

此方法检索并移除此deque队列的第一个元素,或者如果此deque队列为空,则返回null。

21

E pollLast() 

此方法检索并移除此deque队列的最后一个元素,如果此deque队列为空,则返回null。

22

E pop() 

这种方法的此deque队列所表示的堆栈弹出一个元素。

23

void push(E e) 

这种方法将元素推入此deque队列所表示的堆栈。

24

E remove() 

此方法检索并移除此deque队列表示的队列的头部。

25

boolean remove(Object o) 

此方法从此deque队列中移除指定元素的单个实例。

26

E removeFirst() 

此方法检索并移除此deque队列的第一个元素。

27

boolean removeFirstOccurrence(Object o) 

此方法移除此deque队列的指定元素的第一个匹配。

28

E removeLast() 

此方法检索并移除此deque队列的最后一个元素。

29

boolean removeLastOccurrence(Object o) 

此方法移除此deque队列的指定元素的最后一次出现。

30

int size() 

此方法返回在此deque队列的元素个数。

31

object[] toArray() 

这个方法返回一个包含所有在此deque队列在适当的序列中元素的数组。

package com.datastruct; import java.util.ArrayDeque; public class DateStruct { private static final int MIN_INITIAL_CAPACITY = 8;//最小定义 public static void main(String[] args){ // ArrayDeque集合既可当队列使用,也可当栈使用, ArrayDeque stack = new ArrayDeque(); stack.push("循循渐进Linux"); stack.push("小学语文"); stack.push("时间简史"); stack.offer("无敌帅哥"); System.out.println(stack); System.out.println(stack.peek()); System.out.println(stack); System.out.println(stack.pop()); System.out.println(stack); System.out.println(stack.poll()); //poll是队列数据结构实现类的方法,从队首获取元素,同时获取的这个元素将从原队列删除; //pop是栈结构的实现类的方法,表示返回栈顶的元素,同时该元素从栈中删除,当栈中没有元素时,调用该方法会发生异常 } }

 

ArrayDeque类实现了Deque接口,是一个双端队列。不像LinkedList是一个双向链表,ArrayDeque是一个双向数组。这么说有点不准确,应该是,ArrayDeque的底层实现是一个循环数组:什么是循环数组?对于普通的数组,下标为0的元素代表数组的第一个元素,下标最大的元素代表数组的最后一个元素,这是固定的,对于在尾部添加和删除元素的队列操作来说还可以,但是对于同时也要在头部添加和删除元素就不适合了,因为这时必须移动头部元素后面的所有元素。而循环数组就解决了这个问题。

 

其中中间的部分是队列中的元素。从这里我们可以看出,队列中的元素还是在数组中的连续部分存储的。两边空的地方可以扩展队列。如果要在头部添加或删除一个元素,只需要使头下标head左移或右移一位即可,对于尾部的操作也是如此。这样就避免了真个数组的移动,提高了性能。

可以如果头部一直添加,或尾部一直添加,导致左面或右面不够了怎么办?这时就体现出循环数组的循环特点来了。具体的做法是,如果添加头部时左面没空间了,那么就在右面添加。

数组的总长度固定,保证能够容纳所有元素的情况下,两个下标(head和tail)就像这样来回循环着。

那么在这两种情况下,队列中的元素个数怎么计算呢?

对于上面那种情况很容易,元素个数就是tail-head+1。下面这种情况就不是那么清晰了。可以先算中间空余的部分,然后用总长度减去空余的。中间空余的部分的长度是head-tail-1,那么元素个数就是length-(head-tail-1)。

 

事实上,ArrayDeque类的实现者有个更好的方法,使用位运算:

public int size() { return (tail - head) & (elements.length - 1); }

这样就计算了元素的个数,很神奇。可是,head和tail总有相等的时候。一种情况是队列中没有元素,这时head==tail,就像这样:

另一种情况是,head循环后一直增加,一直增加到tail:

这两种情况tail都等于head,看ArrayDeque类的isEmpty方法:

 

public boolean isEmpty() { return head == tail; }

这又是怎么判断到底是空呢还是满呢?

事实上,ArrayDeque类的实现者不会让第二种情况发生。因为ArrayDeque是一个可变长度队列,即如果空间不够,程序会自动扩大数组的容量为原来的2倍,然后将原来的元素复制到新的数组中。

 

还有一点,初始化时这个数组的长度是8,如果满了就扩大为原来的2倍,因此数组的长度一直是2的幂。

 

这个双端队列的基本操作方法都很简单,这里就不过多叙述。

 

虽然双端队列只可以在两端做添加和删除操作,但ArrayDeque类还增加了两个方法可以在中间删除给定的元素:

 

public boolean removeFirstOccurrence(Object o) public boolean removeLastOccurrence(Object o)

第一个方法删除第一次出现给定对象的元素(第一次出现即从head开始到tail结束);第二个方法删除最后一次出现给定对象的元素。

由于双端队列的特殊性(head可能比tail大),如果要从head到tail遍历队列,需要这样:

 

int mask=elements.length-1; int i=head; while(i!=tail) { dosomething with elements[i]; i=(i+1)&mask; }

不过这样的话tail的那个元素没有遍历到。

ArrayDeque类中还定义了两个迭代器,一个是正向的Iterator迭代器,通过方法iterator返回;另一个是反向迭代器DescendingIterator,通过方法descendingIterator返回。关于迭代器的部分在上一节LinkedList部分讲到了,这里不过多重复。ArrayDeque类内部实现Iterator接口的内部类分别是:

private class DeqIterator implements Iterator private class DescendingIterator implements Iterator

这两个类实现了next和hasNext方法。

 

2.3优先级队列(priority queue)

元素可以按照任意的顺序插人,却总是按照排序的顺序进行检索。也就是说,无论何时调用 remove 方法,总会获得当前优先级队列中最小的元素。然而,优先级队列并没有对所有的元素进行排序。如果用迭代的方式处理这些元素,并不需要对它们进行排序。优先级队列使用了一个优雅且高效的数据结构,称为堆(heap)。堆是一个可以自我调整的二叉树,对树执行添加( add) 和删除(remore) 操作, 可以让最小的元素移动到根,而不必花费时间对元素进行排序。与 TreeSet—样,一个优先级队列既可以保存实现了 Comparable 接口的类对象, 也可以保存在构造器中提供的 Comparator 对象。使用优先级队列的典型示例是任务调度。每一个任务有一个优先级,任务以随机顺序添加到队列中。每当启动一个新的任务时,都将优先级最高的任务从队列中删除(由于习惯上将 1 设为“ 最高” 优先级,所以会将最小的元素删除)。

 

PriorityQueue类的底层是使用数组保存数据的:

transient Object[] queue; private int size = 0;

其中size保存元素的个数。使用数组保存一个二叉树,对于一个节点下标为n,则左儿子的下标为2*n+1,右儿子下标为2*(n+1)。而队列中的最小元素保存在queue[0]中。

 

PriorityQueue即可以保存实现了Comparable接口的类对象,也可以保存在构造器中提供比较器的对象。比如,下面的代码使用自定义比较器构造一个优先级队列:

PriorityQueue pq=new PriorityQueueM<>(new Comparator(){ public int compare(Student s1,Student s2){ return s1.getScore()-s2.getScore(); } }

这个比较器比较两个学生的成绩。这是一个匿名内部类。

在最小堆中,最重要的操作就是添加元素或删除元素后如何调整这个堆。基本上,如果添加一个元素,可以先将这个元素放在最下面,然后将这个元素向上移动,知道大于等于它的父节点或称为根节点;还可以将这个元素放在根节点位置,然后将这个元素向下移动,直到小于等于子节点或称为一个叶节点。

 

下面是PriorityQueue类中实现的上移和下移代码,对我们的编程很有帮助:

 

private void siftUp(int k, E x) { if (comparator != null) siftUpUsingComparator(k, x); else siftUpComparable(k, x); } @SuppressWarnings("unchecked") private void siftUpComparable(int k, E x) { Comparable key = (Comparable) x; while (k > 0) { int parent = (k - 1) >>> 1; Object e = queue[parent]; if (key.compareTo((E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = key; } @SuppressWarnings("unchecked") private void siftUpUsingComparator(int k, E x) { while (k > 0) { int parent = (k - 1) >>> 1; Object e = queue[parent]; if (comparator.compare(x, (E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = x; } private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } @SuppressWarnings("unchecked") private void siftDownComparable(int k, E x) { Comparable key = (Comparable)x; int half = size >>> 1; // loop while a non-leaf while (k < half) { int child = (k << 1) + 1; // assume left child is least Object c = queue[child]; int right = child + 1; if (right < size && ((Comparable) c).compareTo((E) queue[right]) > 0) c = queue[child = right]; if (key.compareTo((E) c) <= 0) break; queue[k] = c; k = child; } queue[k] = key; } @SuppressWarnings("unchecked") private void siftDownUsingComparator(int k, E x) { int half = size >>> 1; while (k < half) { int child = (k << 1) + 1; Object c = queue[child]; int right = child + 1; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0) break; queue[k] = c; k = child; } queue[k] = x; }

 

使用优先级队列的典型示例是任务调度。每一个任务都有一个优先级,任务以随机顺序添加到队列中。每当启动一个新的任务时,都将优先级最高的任务从队列中删除(习惯上将1设为最高优先级)。

 

下面的代码展示了PriorityQueue类的使用:

import java.util.*; public class PriorityQueueTest { public static void main(String[] args) { PriorityQueue pq=new PriorityQueue<>(); pq.add(new GregorianCalendar(1906,Calendar.DECEMBER,9)); pq.add(new GregorianCalendar(1815,Calendar.DECEMBER,10)); pq.add(new GregorianCalendar(1903,Calendar.DECEMBER,3)); pq.add(new GregorianCalendar(1910,Calendar.JUNE,22)); System.out.println("Iterating over elements..."); for(GregorianCalendar date:pq){ System.out.println(date.get(Calendar.YEAR)); } System.out.println("Removing elements..."); while(!pq.isEmpty()){ System.out.println(pq.remove().get(Calendar.YEAR)); } } }

 

=============================================================================

 

 

你可能感兴趣的:(Java)