先进先出的数据结构
在FIFO数据结构中,将首先处理添加到队列中的第一个元素。
如上图所示,队列是典型的FIFO数据结构。插入操作也称作入队,新元素始终被添加在队列的末尾。删除操作也称为出队,你只能移除第一个元素。
队列的实现
- 为了实现队列,可以使用动态数组和指向队列头部的索引。
- 队列支持的两种操作:
- 入队
- 出队
- 缺点:实现简单,但在某些情况下效率很低。随着起始指针的移动,元素出队,但并没有在数组中删除,还占用着内存空间。如此以来,浪费的空间越来越多。当我们有空间限制时,这将是难以接受的。
public class Queue {
private List<Integer> queue;
private int head;
public Queue() {
// TODO Auto-generated constructor stub
this.queue = new ArrayList<Integer>();
this.head = 0;
}
public boolean enQueue(int e) {
queue.add(e);
return true;
}
public boolean deQueue() {
if(head < queue.size()) {
System.out.println(queue.get(head)+"已出队!");
head ++;
return true;
}else {
System.out.print("队列已空!");
return false;
}
}
public boolean isEmpty() {
return head >= queue.size();
}
public void printHead() {
System.out.println("首元素是:"+ queue.get(head));
}
public static void main(String[] args) {
Queue queuew = new Queue();
queuew.enQueue(3);
queuew.enQueue(7);
queuew.enQueue(9);
queuew.enQueue(10);
queuew.printHead();
queuew.deQueue();
queuew.deQueue();
queuew.deQueue();
queuew.deQueue();
queuew.deQueue();
}
}
循环队列——数组实现
更有效的方法是使用循环队列。具体来说,我们可以使用固定大小的数组和两个指针来指示起始位置和结束位置。目的是重用我们之前提到的被浪费的内存。
- 出队:front指针循环意义上的加1。公式:front = (front + 1) % capacity
- 入队:rear指针循环意义上的加1。 公式:rear = (front + count) % capacity【(rear + 1) % capacity】
class MyCircularQueue {
private int[] queue;
//队列当前元素的数量
private int count;
//队列的长度
private int capacity;
private int front;
private int rear;
public MyCircularQueue(int k) {
queue = new int[k];
capacity = k;
count = 0;
front = 0;
rear = 0;
}
public boolean enQueue(int value) {
if(isFull()){
return false;
}
rear = (front + count) % capacity;
queue[rear] = value;
count ++;
return true;
}
public boolean deQueue() {
if(isEmpty()){
return false;
}
front = (front + 1) % capacity;
count --;
return true;
}
public int Front() {
return isEmpty() ? -1 : queue[front];
}
public int Rear() {
return isEmpty() ? -1 : queue[rear];
}
public boolean isEmpty() {
return count == 0;
}
public boolean isFull() {
return count == capacity;
}
}
复杂度分析
- 时间复杂度:O(1)。该数据结构中,所有方法都具有恒定的时间复杂度。
- 空间复杂度:O(N),其中 N 是队列的预分配容量。循环队列的整个生命周期中,都持有该预分配的空间。
改进:线程安全
上面实现满足所有的要求,但是可能存在一些风险。从并发性来看,该循环队列是线程不安全的。
例如:下图的执行序列超出了队列的设计容量,会覆盖队尾元素。
class MyCircularQueue {
private int[] queue;
//队列当前元素的数量
private int count;
//队列的长度
private int capacity;
private int front;
private int rear;
private ReentrantLock reenTranLock = new ReentrantLock();;
public MyCircularQueue(int k) {
queue = new int[k];
capacity = k;
count = 0;
front = 0;
rear = 0;
}
public boolean enQueue(int value) {
reenTranLock.lock();
try{
if(isFull()){
return false;
}
rear = (front + count) % capacity;
queue[rear] = value;
count ++;
}finally{
reenTranLock.unlock();
}
return true;
}
单链表实现队列
单链表 和数组都是很常用的数据结构。与固定大小的数组相比,单链表不会为未使用的容量预分配内存,因此它的内存效率更高。
class Node{
int value;
Node next;
public Node(int value){
this.value = value;
this.next = null;
}
}
class MyCircularQueue {
//队列当前元素的数量
private int count;
//队列的长度
private int capacity;
private Node front;
private Node rear;
public MyCircularQueue(int k) {
capacity = k;
count = 0;
front = null;
rear = null;
}
public boolean enQueue(int value) {
if(isFull()){
return false;
}
Node newNode = new Node(value);
if(isEmpty()){
front = rear = newNode;
}else{
rear.next = newNode;
rear = rear.next;
}
count ++;
return true;
}
public boolean deQueue() {
if(isEmpty()){
return false;
}
front = front.next;
count --;
return true;
}
public int Front() {
return isEmpty() ? -1 : front.value;
}
public int Rear() {
return isEmpty() ? -1 : rear.value;
}
public boolean isEmpty() {
return count == 0;
}
public boolean isFull() {
return count == capacity;
}
}
复杂度分析
- 时间复杂度:O(1),所有方法都具有恒定的时间复杂度。
- 空间复杂度:O(N),与数组实现相同。但是单链表实现f方式的内存效率更高。
内置的队列库
使用内置队列库常用的一些操作:
// "static void main" must be defined in a public class.
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());
}
}
队列与广度优先搜索BFS
广度优先搜索(BFS)的一个常见应用是找出从根结点到目标结点的最短路径。在本文中,我们提供了一个示例来解释在 BFS 算法中是如何逐步应用队列的。
作者:LeetCode
链接:https://leetcode-cn.com/problems/design-circular-queue/solution/she-ji-xun-huan-dui-lie-by-leetcode/
来源:力扣(LeetCode)