Java 容器类(四)——Queue

一、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 方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值