队列,和栈一样,也是一种对数据的"存"和"取"有严格要求的线性存储结构。
与栈结构不同的是,队列的两端都"开口",要求数据只能从一端进,从另一端出
通常,称进数据的一端为 "队尾",出数据的一端为 "队头",数据元素进队列的过程称为 "入队",出队列的过程称为 "出队"。
不仅如此,队列中数据的进出要遵循 "先进先出" 的原则,即最先进队列的数据元素,同样要最先出队列。拿图 1 中的队列来说,从数据在队列中的存储状态可以分析出,元素 1 最先进队,其次是元素 2,最后是元素 3。此时如果将元素 3 出队,根据队列 "先进先出" 的特点,元素 1 要先出队列,元素 2 再出队列,最后才轮到元素 3 出队列。
栈和队列不要混淆,栈结构是一端封口,特点是"先进后出";而队列的两端全是开口,特点是"先进先出"。
因此,数据从表的一端进,从另一端出,且遵循 "先进先出" 原则的线性存储结构就是队列。
队列的实现
队列存储结构的实现有以下两种方式:
- 顺序队列:在顺序表的基础上实现的队列结构;
- 链队列:在链表的基础上实现的队列结构;
两者的区别仅是顺序表和链表的区别,即在实际的物理空间中,数据集中存储的队列是顺序队列,分散存储的队列是链队列。
顺序队列简单实现
由于顺序队列的底层使用的是数组,因此需预先申请一块足够大的内存空间初始化顺序队列。除此之外,为了满足顺序队列中数据从队尾进,队头出且先进先出的要求,我们还需要定义两个指针(top 和 rear)分别用于指向顺序队列中的队头元素和队尾元素
由于顺序队列初始状态没有存储任何元素,因此 top 指针和 rear 指针重合,且由于顺序队列底层实现靠的是数组,因此 top 和 rear 实际上是两个变量,它的值分别是队头元素和队尾元素所在数组位置的下标。
当有数据元素进队列时,对应的实现操作是将其存储在指针 rear 指向的数组位置,然后 rear+1;当需要队头元素出队时,仅需做 top+1 操作。
{1,2,3,4} 用顺序队列存储的实现操作
顺序队列中数据出队列的实现过程
此方法存在的问题
我们希望的是当 rear和top相等的时候就说队列为空。可是通过数据全部出队后的示意图。会发现,指针 top 和 rear 重合位置指向了 a[4] 而不再是 a[0]。也就是说,整个顺序队列在数据不断地进队出队过程中,在顺序表中的位置不断后移。
顺序队列整体后移造成的影响是:
- 顺序队列之前的数组存储空间将无法再被使用,造成了空间浪费;
- 如果顺序表申请的空间不足够大,则直接造成程序中数组 a 溢出,产生溢出错误;
循环队列简单实现
既然明白了上面这种方法的弊端,那么我们可以试着在它的基础上对其改良。
为了解决以上两个问题,可以使用巧妙的方法将顺序表打造成一个环状表
top和rear相等时说明队列为空。(rear+1)% 数组长度 == top时说明队满
比如上面这个当rear在a[5]时说明队满。会有一个空间浪费
java实现循环队列
1 package com.qiaorui; 2 3 public class LoopQueue<E> { 4 private E data[]; 5 private int top, rear; 6 7 public LoopQueue(int capacity) { 8 this.data = (E[]) new Object[capacity + 1]; 9 this.top = 0; // 头指针 10 this.rear = 0; // 尾指针 11 } 12 13 public LoopQueue() { 14 this(10); 15 } 16 17 public int getCapacity() { 18 return data.length - 1; 19 } 20 21 public boolean isEmpty() { 22 return top == rear; 23 } 24 25 public void enqueue(E e) { 26 if ((rear + 1) % data.length == top) { 27 resize(getCapacity() * 2); //扩容 28 } 29 data[rear] = e; 30 rear = (rear + 1) % data.length; 31 } 32 33 public E dequeue() { 34 if (isEmpty()) 35 throw new IllegalArgumentException("Cannot dequeue from an empty queue."); 36 E ret = data[top]; 37 data[top] = null; 38 top = (top + 1) % data.length; 39 if ((rear - top + data.length) % data.length == getCapacity() / 4 && getCapacity() / 2 != 0) 40 resize(getCapacity() / 2); 41 return ret; 42 } 43 44 public E getFront() { 45 if (isEmpty()) 46 throw new IllegalArgumentException("Queue is empty."); 47 return data[top]; 48 } 49 50 private void resize(int newCapacity) { 51 E[] newData = (E[]) new Object[newCapacity + 1]; 52 //如果rear<top结果是rear-top+maxsize 53 //如果rear>top结果是rear-top 54 //为了用一个表达式同时表达两者,用(rear-top+maxsize)%maxsize 55 for (int i = 0; i < (rear - top + data.length) % data.length; i++) 56 newData[i] = data[(i + top) % data.length]; //从头指针开始把元素放入新的数组中 57 this.rear = (rear - top + data.length) % data.length; //这个一定要在缩容前赋值 58 this.data = newData; //缩容 59 this.top = 0; 60 61 } 62 63 public String toString() { 64 65 StringBuilder res = new StringBuilder(); 66 res.append(String.format("Queue: size = %d , capacity = %d ", (rear - top + data.length) % data.length, getCapacity())); 67 res.append("front ["); 68 //遍历方式 2 69 for (int i = top; i != rear; i = (i + 1) % data.length) { 70 res.append(data[i]); 71 if ((i + 1) % data.length != rear) { 72 res.append(", "); 73 } 74 } 75 res.append("] tail"); 76 return res.toString(); 77 } 78 79 public static void main(String[] args) { 80 81 LoopQueue<Integer> queue = new LoopQueue(); 82 for (int i = 0; i < 10; i++) { 83 queue.enqueue(i); 84 System.out.println(queue); 85 86 if (i % 3 == 2) { 87 queue.dequeue(); 88 System.out.println(queue); 89 } 90 } 91 } 92 93 }
简单使用
力扣 933题 https://leetcode-cn.com/problems/number-of-recent-calls/
1 class RecentCounter { 2 private Queue<Integer> queue =new LinkedList<Integer>(); 3 4 public RecentCounter() { 5 6 } 7 8 public int ping(int t) { 9 queue.offer(t); 10 while (queue.peek() < t - 3000) 11 queue.poll(); 12 return queue.size(); 13 } 14 }