队列
队列
- 先进先出(FIFO)
- 在 FIFO 数据结构中,将首先处理添加到队列中的第一个元素。
- 队列是典型的 FIFO 数据结构
- 插入(insert)操作也称作入队(enqueue),新元素始终被添加在队列的末尾
- 删除(delete)操作也被称为出队(dequeue)。 你只能移除第一个元素
队列实现如下:
import java.util.ArrayList;
import java.util.List;
/**
* 为了实现队列,我们可以使用动态数组和指向队列头部的索引。
* 如上所述,队列应支持两种操作:
* 入队和出队。入队会向队列追加一个新元素,而出队会删除第一个元素。
* 所以我们需要一个索引来指出起点。
* 实现很简单,但在某些情况下效率很低。
* 随着起始指针的移动,浪费了越来越多的空间。
* 当我们有空间限制时,这将是难以接受的。
*/
class MyQueue {
// store elements
private List<Integer> data;
// a pointer to indicate the start position
private int p_start;
public MyQueue() {
data = new ArrayList<Integer>();
p_start = 0;
}
/** Insert an element into the queue. Return true if the operation is successful. */
public boolean enQueue(int x) {
data.add(x);
return true;
};
/** Delete an element from the queue. Return true if the operation is successful. */
public boolean deQueue() {
if (isEmpty() == true) {
return false;
}
p_start++;
return true;
}
/** Get the front item from the queue. */
public int Front() {
return data.get(p_start);
}
/** Checks whether the queue is empty or not. */
public boolean isEmpty() {
return p_start >= data.size();
}
};
public class Main {
public static void main(String[] args) {
MyQueue q = new MyQueue();
q.enQueue(5);
q.enQueue(3);
if (q.isEmpty() == false) {
System.out.println(q.Front());
}
q.deQueue();
if (q.isEmpty() == false) {
System.out.println(q.Front());
}
q.deQueue();
if (q.isEmpty() == false) {
System.out.println(q.Front());
}
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 更有效的方法是使用循环队列。
* 具体来说,我们可以使用固定大小的数组和两个指针来指示起始位置和结束位置。
* 目的是重用我们之前提到的被浪费的存储。
*/
class MyCircularQueue {
private int[] queue; // 一个固定大小的数组,用于保存循环队列的元素。
private int headIndex; // 一个整数,保存队首 head 的索引。
private int count; // 循环队列当前的长度,即循环队列中的元素数量。
// 使用 hadIndex 和 count 可以计算出队尾元素的索引,因此不需要队尾属性。
private int capacity; // 循环队列的容量,即队列中最多可以容纳的元素数量。
/** Initialize your data structure here. Set the size of the queue to be k. */
public MyCircularQueue(int k) {
this.capacity = k;
this.queue = new int[k];
this.headIndex = 0;
this.count = 0;
}
/** Insert an element into the circular queue. Return true if the operation is successful. */
public boolean enQueue(int value) {
if (this.count == this.capacity)
return false;
this.queue[(this.headIndex + this.count) % this.capacity] = value;
this.count += 1;
return true;
}
/** Delete an element from the circular queue. Return true if the operation is successful. */
public boolean deQueue() {
if (this.count == 0)
return false;
this.headIndex = (this.headIndex + 1) % this.capacity;
this.count -= 1;
return true;
}
/** Get the front item from the queue. */
public int Front() {
if (this.count == 0)
return -1;
return this.queue[this.headIndex];
}
/** Get the last item from the queue. */
public int Rear() {
if (this.count == 0)
return -1;
int tailIndex = (this.headIndex + this.count - 1) % this.capacity;
return this.queue[tailIndex];
}
/** Checks whether the circular queue is empty or not. */
public boolean isEmpty() {
return (this.count == 0);
}
/** Checks whether the circular queue is full or not. */
public boolean isFull() {
return (this.count == this.capacity);
}
}
内置队列库如下:
public class Main {
public static void main(String[] args) {
// 1. Initialize a queue.
Queue<Integer> q = new LinkedList();
// 2. Get the first element - return null if queue is empty.
System.out.println("The first element is: " + q.peek());
// 3. Push new element.
q.offer(5);
q.offer(13);
q.offer(8);
q.offer(6);
// 4. Pop an element.
q.poll();
// 5. Get the first element.
System.out.println("The first element is: " + q.peek());
// 7. Get the size of the queue.
System.out.println("The size is: " + q.size());
}
}
//假如队列不空,删除并返回这个队列头部的元素。如果队列是空的,返回null
q.poll();
//假如队列不空,删除并返回这个队列头部的元素。如果队列是空的,抛出NoSuchElementException
q.remove();
//假如队列不空,返回这个队列头部的元素,但不删除。如果队列是空的,返回null
q.peek();
//假如队列不空,返回这个队列头部的元素,但不删除。如果队列是空的,返回NoSuchElementException
q.element();
// 结果:
The first element is: null
The first element is: 13
The size is: 3
将集合的接口与实现分离(队列举例)
- 实现方式:使用循环数组、使用链表
- 队列可以让人们有效地在尾部添加一个元素,在头部删除一个元素。
- 有两个端头的队列,即双端队列。可以在头部、尾部同时添加或删除元素。
- 不支持在队列中间添加元素。
- 在Java SE 6中引入了Deque接口,并由ArrayDeque和LinkedList类实现。这两个类都提供了双端队列。
- 必要时可以增加队列的长度
- 如果需要一个循环数组队列,就可以使用ArrayDeque类
- 如果需要一个链表数组,就直接使用LinkedList类,这个类实现了Queue接口
- 在程序中使用队列时,一旦构建了集合就不需要具体使用了哪种实现。所以,只有在构建集合对象时,使用具体的类才有意义。可以使用接口类型存放集合的引用。
Queue<Customer> expressLane = new CircularArrayQueue<>(100);
expressLane.add(new Customer("Harry"));
// 使用这种方式,可以轻松使用另外一种方式实现,只需要在调用构造器的地方修改
Queue<Customer> expressLane = new LinkedListQueue<>(100);
expressLane.add(new Customer("Harry"));
- 接口本身并不能说明哪种实现效率如何。
- 循环数组要比链表更高效,但是也需要付出代价。
- 循环数组是一个有界集合,即容量有限。如果程序中要收集的对象数量没有上限,就最好使用链表来实现。
- 有一组名字以Abstract开头的类,例如,AbstractQueue。如果想实现自己的队列类,扩展AbstractQueue类比实现Queue接口中的所有方法更轻松。
优先级队列:
- 优先级队列中的元素可以按照任意的顺序插入,却总是按照排序的顺序进行检索
- 无论何时调用remove方法,总会获得当前优先级队列中最小的元素
- 然而,优先级队列并没有对所有的元素进行排序。
- 如果使用迭代的方式处理这些元素,并不需要对它们进行排序。
- 优先级队列使用了一个优雅且高效的数据结构:堆(heap)
- 堆是一个可以自我调整的二叉树,对树执行添加(add)和删除(remove)操作,可以让最小的元素移动到根,而不必花费时间对元素进行排序。
- 使用优先级队列的典型示例:任务调度。
- 每一个任务有一个优先级,任务以随机顺序添加到队列中。每当启动一个新的任务时,都将优先级最高的任务从队列中删除(由于习惯上将1设置为最高优先级,所以会将最小的元素删除)
面试题
什么是Java优先级队列(Priority Queue)?
- Priority Queue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。
- 在创建的时候,我们可以给它提供一个负责给元素排序的比较器。
- Priority Queue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。
- 最后,Priority Queue不是线程安全的,入队和出队的时间复杂度是O(log(n))。
https://leetcode-cn.com/leetbook/read/queue-stack/kkqf1/
Java核心技术卷Ⅰ (第10版)第九章