文章目录
一、Queue 概述
队列(Queue)是一个先进先出(FIFO)的容器,即从容器的一端放入事物,从另一端取出,并且存放的顺序和取出的顺序是一样的。插入的一端叫队尾,删除的一端叫队头,因此,队列不允许随机访问队列中的元素。Java 中 Queue 的实现类只有 PriorityQueue(优先级队列)和 LinkedList,另外还有一个子接口 Deque,表示双端队列。
二、Queue 接口的常用方法
- void add(Object e):将指定元素加入此队列的尾部;
- Object element():获取队列头部的元素,但是不删除该元素;
- boolean offer(Object e):将指定的元素插入此队列的尾部;
- Object peek():返回队列头部的元素,但是不删除该元素。如果队列为空,则返回null;
- Object poll():返回队列头部的元素,并删除该元素。如果队列为空,则返回null;
- Object remove():获取队列头部的元素,并删除该元素。
LinkedList 提供了上述方法以支持队列的行为,并且它实现了 Queue 接口,因此 LinkedList 可以作为 Queue 的一种实现。LinkedList 也可以向上转型为 Queue,如
Queue<Integer> queue = new LinkedList<Integer>();
三、PriorityQueue——优先级队列
PriorityQueue 保存队列元素的顺序不是按加入队列的顺序,而是按队列元素的大小进行升序排序。优先级高的排在前面,优先级低的排在后面。因此,当调用 peek() 或 poll() 方法取队头元素时,并不是取出最先进入队列的元素,而是取出队列中最小(优先级最高的)的元素。(注: PriorityQueue 已经不再是简单的先进先出的队列了)
使用 Queue 存储基本数据类型(当然存储的是其包装类型)时,也就默认使用自然排序的升序排序方式;而存储自定义对象时,就要自己重新定义比较规则来决定排序的方式,有自然排序和定制排序两种方式。
参考 TreeSet 测试:
容器类(二)——Set
1、默认的自然排序(升序)
Queue<Integer> queue = new PriorityQueue<>();
queue.add(4);
queue.add(43);
queue.add(23);
queue.add(15);
System.out.println(queue);
// 结果:
[4, 15, 23, 43]
2、自然排序——实现 Comparable 接口
PriorityQueue 存储自定义类时,自定义类要实现 Comparable 接口并重写 compareTo() 方法,在 compareTo() 方法中定义比较规则,决定排序的方式。测试如下:
// 学生类,有姓名和成绩两个属性,实现Comparable接口自定义比较规则
class Stu implements Comparable<Stu>{
private String name;
private int score;
public Stu(){ super(); }
public Stu(String name, int score){
super();
this.name = name;
this.score = score;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getScore() { return score; }
public void setScore(int score) { this.score = score; }
public String toString(){
return "[" + name + ", " + score + "]";
}
@Override
// 先比较成绩,再比较姓名
public int compareTo(Stu o){
if(this.getScore() == o.getScore()){
return this.getName().compareTo(o.getName());
}else{
return this.getScore() - o.getScore();
}
}
}
public class PriorityQueueTest3 {
public static void main(String[] args) {
Queue<Stu> queue = new PriorityQueue<>();
queue.add(new Stu("B", 98));
queue.add(new Stu("A", 98));
queue.add(new Stu("C", 89));
queue.add(new Stu("D", 93));
queue.add(new Stu("F", 98));
// 依次出队
while(!queue.isEmpty()){
System.out.println(queue.poll());
}
}
}
测试结果:
[C, 89]
[D, 93]
[A, 98]
[B, 98]
[F, 98]
3、定制排序——实现 Comparator 接口
PriorityQueue 存储自定义类时,也可以自定义一个外比较器,实现 Comparator 接口并重写 compare() 方法。测试如下:
// 篮球明星类,有姓名和号码两个属性
class Star{
private String name;
private int number;
public Star(){}
public Star(String name, int number) {
super();
this.name = name;
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public String toString(){
return "[" + name + ", " + number + "]";
}
}
// 创建比较器
class Star_Comparator implements Comparator<Star>{
@Override
// 先比较号码,再比较姓名
public int compare(Star o1, Star o2){
if(o1.getNumber() == o2.getNumber()){
return o1.getName().compareTo(o2.getName());
}else{
return o1.getNumber() - o2.getNumber();
}
}
}
public class PriorityQueueTest2 {
public static void main(String[] args) {
Queue<Star> queue = new PriorityQueue<>(new Star_Comparator());
queue.add(new Star("kobe", 24));
queue.add(new Star("curry", 30));
queue.add(new Star("pual", 3));
queue.add(new Star("wade", 3));
queue.add(new Star("jordan", 23));
while(!queue.isEmpty()){
System.out.println(queue.poll());// 出队
}
}
}
测试结果:
[pual, 3]
[wade, 3]
[jordan, 23]
[kobe, 24]
[curry, 30]
四、Deque——双端队列
Deque 是 Queue 的子接口,用来表示双端队列。Deque 的实现类有 LinkedList 和 ArrayDeque,能够实现队列和栈的功能。
LinkedList 类在 容器类(一)——List 中有详细介绍。
1、ArrayDeque 类
ArrayDeque 类是 Deque 接口的大小可变数组的实现。为了满足可以同时在数组两端插入或删除元素的需求,该数组以循环数组的方式实现。
循环队列的原理可参考:Java数据结构与算法——队列(queue)
注:
- ArrayDeque 没有容量限制,它可根据需要增加容量以支持使用;
- ArrayDeque 禁止 null 元素;
- ArrayDeque 很可能在用作堆栈时快于 Stack,在用作队列时快于 LinkedList(因为它是基于循环数组的实现,只需操作索引)。
- 创建一个 ArrayDeque 时,使用 ArrayDeque() 可构造一个初始容量能够容纳 16 个元素的空数组双端队列;使用 ArrayDeque(int numElements) 可构造一个初始容量能够容纳指定数量的元素的空数组双端队列。
2、利用 Deque 实现队列和栈
利用 LinkedList 和 ArrayDeque 实现队列和栈的功能时,使用的方法是完全相同的。
(1)将 Deque 用作队列
将双端队列用作队列时,将得到 FIFO(先进先出)行为。将元素添加到双端队列的末尾,从双端队列的开头移除元素。从 Queue 接口继承的方法完全等效于 Deque 方法,如下表所示:
Queue 方法 | 等效 Deque 方法 |
---|---|
add(e) | addLast(e) |
offer(e) | offerLast(e) |
remove() | removeFirst() |
poll() | pollFirst() |
element() | getFirst() |
peek() | peekFirst() |
测试代码:
Deque<Integer> queue = new LinkedList<>();
// Deque<Integer> queue= new ArrayDeque<>();
queue.addLast(5);// queue[5]
queue.addLast(2);// queue[5, 2]
queue.addLast(4);// queue[5, 2, 4]
queue.addLast(3);// queue[5, 2, 4, 3]
System.out.println(queue.pollFirst());// queue[2, 4, 3]
System.out.println(queue.pollFirst());// queue[4, 3]
(2)将 Deque 用作栈
双端队列也可用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留的 Stack 类。在将双端队列用作堆栈时,元素被推入双端队列的开头并从双端队列开头弹出。堆栈方法完全等效于 Deque 方法,如下表所示:
堆栈方法 | 等效 Deque 方法 |
---|---|
push(e) | addFirst(e) |
pop() | removeFirst() |
peek() | peekFirst() |
测试代码:
Deque<Integer> stack = new LinkedList<>();
//Deque<Integer> stack = new ArrayDeque<>();
stack.addFirst(3);
stack.addFirst(6);
stack.addFirst(9);
System.out.println(stack);
System.out.println(stack.peekFirst());// 返回栈顶元素并不删除
System.out.println(stack.removeFirst());// 返回栈顶元素并删除该元素
System.out.println(stack);
结果:
[9, 6, 3]
9
9
[6, 3]
(3)注意几点
(1)在将双端队列用作队列或堆栈时,peek 方法同样正常工作;无论哪种情况下,都从双端队列的开头抽取元素。
(2)与 List 接口不同,此接口不支持通过索引访问元素。
(3)虽然 Deque 实现没有严格要求禁止插入 null 元素,但建议最好这样做。建议任何事实上允许 null 元素的 Deque 实现用户最好不要利用插入 null 的功能。这是因为各种方法会将 null 用作特殊的返回值来指示双端队列为空。
(4)Deque 实现通常不定义基于元素的 equals 和 hashCode 方法,而是从 Object 类继承 equals 和 hashCode 方法。